An important facet of system design is the manner in which objects are created. Although far more time is often spent considering the object model and instance interaction, if this simple design aspect is ignored it will adversely impact the entire system. Thus, it is not only important what an object does or what it models, but also in what manner it was created.
One of the most widely used creational patterns is the Factory. This pattern is aptly named, as it calls for the use of a specialized object solely to create other objects, much like a real-world factory.
The idea behind the Factory Method pattern is that it allows for the case where a client doesn't know what concrete classes it will be required to create at runtime, but just wants to get a class that will do the job. You'll see factories used in logging frameworks, and in a lot of scenarios where the client doesn't need to know about the concrete implementations. It's a good approach to encapsulation.
As with other design patterns, there are countless variations of the Factory pattern, although most variants typically used the same set of primary actors, a client, a factory, and a product. The client is an object that requires an instance of another object (the product) for some purpose. Rather than creating the product instance directly, the client delegates this responsibility to the factory. Once invoked, the factory creates a new instance of the product, passing it back to the client. Put simply, the client uses the factory to create an instance of the product. Figure 1 shows this logical relationship between these elements of the pattern.
The factory completely abstracts the creation and initialization of the product from the client. This indirection enables the client to focus on its discrete role in the application without concerning itself with the details of how the product is created. Thus, as the product implementation changes over time, the client remains unchanged.
Consider an application that models the assembly of a variety of personal computers. The application contains a ComputerAssembler class that is responsible for the assembly of the computer, a Computer class that models the computer being built, and a ComputerFactory class that creates instances of the Computer class. In using the Factory pattern, the ComputerAssembler class delegates responsibility for creation of Computer instances to the ComputerFactory. This ensures that if the instantiation and/or initialization process changes (e.g. new constructor, use of an activator, custom pooling, etc.), the ComputerAssembler does not need to change at all. This is the benefit of abstracting the creation of an object from its use.
In addition, suppose that the business requirements changed and a new type of computer needs to be assembled. Rather than modifying the ComputerAssembler class directly, the ComputerFactory class can be modified to create instances of a new computer class (assuming that this new class has the same interface as Computer). Furthermore, it is also possible to address this requirement by creating a new factory class (that has the same interface as ComputerFactory) that creates instances of the new computer class (again, with the same interface as Computer). As before, nothing in the ComputerAssembler class needs to change, all the logic just continues to work as it had previously. This is the benefit of abstracting the types of the product and factory into invariant interfaces.
Most implementations of the Factory pattern use two abstract classes, Factory and Product, to provide the invariant interfaces discussed in the logical model. Although we could utilize pure interfaces for this purpose, the use of abstract classes enables us to place common instance behavior in the abstract class.
As shown in Figure 2, the Client uses an instance of a concrete subclass of Factory (ConcreteFactory) to create an instance of a concrete Product subclass (ConcreteProduct). Since all variables, parameters, and method return values are typed to the Factory and Product abstract classes, the Client is unaware of the introduction of these subclasses. This enables the introduction of new Factory and Product subclasses as requirements warrant; nothing needs to change within the Client. It is worth noting that this pattern does not dictate the use of the specific names (i.e. client, factory, product) used within the physical model, rather the use of these names is only to provide clarity in terms of the role each type plays in the pattern.
Using our previously defined computer assembly application, let's examine a simple implementation of the Factory pattern
package com.example.factorypettern.computer; public abstract class Computer { abstract public int getMhz(); } } package com.example.factorypettern.factory; import com.example.factorypettern.computer.Computer; public abstract class ComputerFactory { public abstract Computer getComputer(); }
The above code fragment outlines the Computer and ComputerFactory abstract classes. These types represent the invariant factory and product interfaces required by the physical model. The ConcreteComputer and ConcreteComputerFactory classes found below extend these abstract classes. The ConcreteComputer class overrides the Mhz property of the Computer class, returning the value of 500. The ConceteComputerFactory overrides the GetComputer method of the ComputerFactory class returning a new instance of ConcreteComputer.
package com.example.factorypettern.computer; public class ConcreteComputer extends Computer { private int mhz = 50; @Override public int getMhz() { return mhz; } } package com.example.factorypettern.factory; import com.example.factorypettern.computer.Computer; import com.example.factorypettern.computer.ConcreteComputer; public class ConcreteComputerFactory extends ComputerFactory { @Override public Computer getComputer() { return new ConcreteComputer(); } }
The ComputerAssembler class, found below, provides one method, AssembleComputer. This method accepts one parameter that must be an object whose class extends the ComputerFactory abstractclass. This method then uses this factory instance to create an instance of some class that extends the Computer abstract class. Although the ComputerAssembler is blissfully unaware of the actual subclass of Computer that it is using, it is still able to print its type (via type introspection) and determine the MHz of the computer. The Appclass, also found below, is the entry point for the sample. It creates an instance of the ComputerAssembler and invokes the AssembleComputer method passing an instance of a class that extends ComputerFactory.
package com.example.factorypettern.computerassemblers; import com.example.factorypettern.computer.Computer; import com.example.factorypettern.factory.ComputerFactory; public class ComputerAssembler { public void assembleComputer(ComputerFactory computerFactory){ Computer computer=computerFactory.getComputer(); String computerName = computer.getClass().getSimpleName(); System.out.printf("assembled a %s running at %s MHz",computerName,computer.getMhz()); } } package com.example.factorypettern; import com.example.factorypettern.computerassemblers.ComputerAssembler; import com.example.factorypettern.factory.ComputerFactory; import com.example.factorypettern.factory.ConcreteComputerFactory; public class App { public static void main(String[] args) { final ComputerFactory computerFactory=new ConcreteComputerFactory(); final ComputerAssembler computerAssembler=new ComputerAssembler(); computerAssembler.assembleComputer(computerFactory); } }
After program running result is next:
assembled a ConcreteComputer running at 50 MHz
Process finished with exit code 0
In order to illustrate the benefits of factory and product type abstraction, suppose that the application was required to assemble two different computer types, rather than only one. For the purposes of this example, we create an additional factory, BrandXFactory, and a related product class, BrandXComputer, as shown below.
package com.example.factorypettern.computer; public class BrandXComputer extends Computer { private int mhz = 1500; @Override public int getMhz() { return mhz; } } package com.example.factorypettern.factory; import com.example.factorypettern.computer.BrandXComputer; import com.example.factorypettern.computer.Computer; public class BrandXFactory extends ComputerFactory { @Override public Computer getComputer() { return new BrandXComputer(); } }
With the new factory and product in place, we now need to modify the main method of the App to determine which factory to use. If the appropriate string is passed in on the command line, the BrandXFactory will be used; if there is no such command line argument, the standard ComputerFactory will be used. This modification is shown below.
package com.example.factorypettern; import com.example.factorypettern.factory.BrandXFactory; import com.example.factorypettern.factory.ComputerFactory; import com.example.factorypettern.factory.ConcreteComputerFactory; import java.util.Scanner; public class App { public static void main(String[] args) { ComputerFactory computerFactory = null; final Scanner scanner = new Scanner(System.in); final String lineFromConsole = scanner.nextLine(); if ("BrandX".equals(lineFromConsole)) { computerFactory = new BrandXFactory(); } else { computerFactory = new ConcreteComputerFactory(); } } }
Please note that there is no modification or re-implementation of the ComputerAssembler class whatsoever. This class is completely abstracted from the classes of the instances it assembles, as well as the creation and initialization of the same.*1
By now, you should be able to count the main advantages of using factory pattern. Lets note down:
The creation of an object precludes its reuse without significant duplication of code.
The creation of an object requires access to information or resources that should not be contained within the composing class.
The lifetime management of the generated objects must be centralized to ensure a consistent behavior within the application.
Factory pattern is most suitable where there is some complex object creation steps are involved. To ensure that these steps are centralized and not exposed to composing classes, factory pattern should be used. We can see many examples of factory pattern in JDK itself e.g.
java.sql.DriverManager#getConnection()
java.net.URL#openConnection()
java.lang.Class#newInstance()
java.lang.Class#forName()*2
*1 Exploring the Factory Design Pattern
*2 Factory design pattern in java