This pattern was previously described in GoF95.
In general, all subclasses in a class hierarchy inherit the methods implemented by the parent class. A subclass may override the parent class implementation to offer a different type of functionality for the same method. When an application object is aware of the exact functionality it needs, it can directly instantiate the class from the class hierarchy that offers the required functionality.
At times, an application object may only know that it needs to access a class from within the class hierarchy, but does not know exactly which class from among the set of subclasses of the parent class is to be selected. The choice of an appropriate class may depend on factors such as:
In such cases, an application object needs to implement the class selection criteria to instantiate an appropriate class from the hierarchy to access its services (Figure 10.1).
This type of design has the following disadvantages:
Figure 10.1 Client Object Directly Accessing a Service Provider Class Hierarchy
In such cases, the Factory Method pattern recommends encapsulating the functionality required, to select and instantiate an appropriate class, inside a designated method referred to as a factory method. Thus, a factory method can be defined as a method in a class that:
Encapsulation of the required implementation to select and instantiate an appropriate class in a separate method has the following advantages:
Figure 10.2 A Client Object Accessing a Service Provider Class Hierarchy Using a Factory Method
One of the simplest ways of designing a factory method is to create an abstract class or an interface that just declares the factory method. Different subclasses (or implementer classes in the case of an interface) can be designed to implement the factory method in its entirety as depicted in Figure 10.2. Another strategy is to create a concrete creator class with default implementation for the factory method in it. Different subclasses of this concrete class can override the factory method to implement specialized class selection criteria.
Let us design the functionality to log messages in an application. In general, logging messages is one of the most commonly performed tasks in software applications. Logging appropriate messages at appropriate stages can be extremely useful for debugging and monitoring applications.
Because the message logging functionality could be needed by many different clients, it would be a good idea to keep the actual message logging functionality inside a common utility class so that client objects do not have to repeat these details.
Let us define a Java interface Logger (Listing 10.1) that declares the interface to be used by the client objects to log messages.
In general, an incoming message could be logged to different output media, in different formats. Different concrete implementer classes of the Logger interface can handle these differences in implementation. Let us define two such implementers as in Table 10.1. The resulting class hierarchy is depicted in Figure 10.3.
Each of the Logger implementer classes (Listing 10.2) offers the respective functionality stated in Table 10.1 inside the log method declared by the Logger interface.
Consider an application object LoggerTest that intends to use the services provided by the Logger implementers. Suppose that the overall application message logging configuration can be specified using the logger.properties property file.
Listing 10.1 Logger Interface
public interface Logger {
public void log(String msg);
}
Table 10.1 Logger Implementers
Figure 10.3 Message Logging Utility Class Hierarchy
Listing 10.2 Logger Implementer Classes
public class FileLogger implements Logger {
public void log(String msg) {
FileUtil futil = new FileUtil();
futil.writeToFile("log.txt”, msg, true, true);
}
}
public class ConsoleLogger implements Logger {
public void log(String msg) {
System.out.println(msg);
}
}
Sample logger.properties file contents
FileLogging=OFF
Depending on the value of the FileLogging property, an appropriate Logger implementer needs to be used to log messages. For example, if the FileLogging property is set to ON, messages are to be logged to a file and hence a FileLogger object can be used to log messages. Similarly, if the FileLogging property is set to OFF, messages are to be displayed on the console and hence a ConsoleLogger object can be used.
To log messages, an application object such as the LoggerTest needs to:
This requires every application object to:
Figure 10.4 depicts this design strategy.
Applying the Factory Method pattern, the necessary implementation for selecting and instantiating an appropriate Logger implementer can be encapsulated inside a separate getLogger method in a separate class LoggerFactory (Listing 10.3). The LoggerFactory, with the getLogger factory method, plays the role of the ConcreteCreator shown in Figure 10.2.
Figure 10.4 Client LoggerTest Accessing Logger Implementers Directly
As part of its implementation, the factory method getLogger checks the logger.properties property file to see if file logging is enabled and decides which Logger implementation is to be instantiated. The selected Logger implementer instance is returned as an object of type Logger.
With the factory method in place, client objects do not need to deal with the intricacies involved in selecting and instantiating an appropriate Logger implementer. Client objects do not need to know the existence of different implementers of the Logger interface and their associated functionality (Figure 10.5).
Whenever a client object such as the LoggerTest (Listing 10.4) needs to log a message, it can:
Figure 10.6 shows the message flow when the client object LoggerTest uses the LoggerFactory factory method to create an appropriate Logger implementer to log a message.
In this example application design, the creator class LoggerFactory is designed as a concrete class with default implementation for the factory method getLogger. There can be variations in the way in which the class selection criterion is implemented. Such variations can be implemented by overriding the getLogger method in LoggerFactory subclasses.
Listing 10.3 LoggerFactory Class
public class LoggerFactory {
public boolean isFileLoggingEnabled() {
Properties p = new Properties();
try {
p.load(ClassLoader.getSystemResourceAsStream(
"Logger.properties"));
String fileLoggingValue =
p.getProperty("FileLogging");
if (fileLoggingValue.equalsIgnoreCase("ON") == true)
return true;
else
return false;
} catch (IOException e) {
return false;
}
}
//Factory Method
public Logger getLogger() {
if (isFileLoggingEnabled()) {
return new FileLogger();
} else {
return new ConsoleLogger();
}
}
}
1. Add a new logger DBLogger that logs messages to a database.
2. Create a subclass of the LoggerFactory class and override the getLogger implementation to implement a different class selection criterion.
Figure 10.5 The Client LoggerTest Accessing the Logger Class Hierarchy after the Factory Method Pattern Is Applied
Listing 10.4 Client LoggerTest Class
public class LoggerTest {
public static void main(String[] args) {
LoggerFactory factory = new LoggerFactory();
Logger logger = factory.getLogger();
logger.log("A Message to Log");
}
}
Figure 10.6 Message Flow When a Client Uses the LoggerFactory to Create an Appropriate Logger to Log a Message
3.22.77.149