Java: The Factory Method Pattern
Explored the Factory Method pattern, walk through a Java example, and examine some other patterns that can be good alternatives to it.
Join the DZone community and get the full member experience.
Join For FreePatterns are one of the most useful techniques that an experienced developer can employ when writing large volumes of code or when looking for a clean solution to a difficult problem. They can also be one of the most widely overused techniques or the most fatally misunderstood techniques. In the previous articles in this series, we explored the Observer pattern and the Strategy pattern, delving into the purpose and idiosyncrasies of each. In this article, we will focus on the Factory Method pattern, introducing its textbook definition and concentrating on an intuitive understanding of the pattern. We will also explore the common mischaracterizations of this pattern and define some of the colloquial uses of this pattern.
The Textbook Definition
The Factory Method pattern has commonly been misunderstood to mean any method whose sole purpose is to construct an object and return this created object. In most cases, this translates to a static method that abstracts the construction process of an object. Even the Spring framework uses this loose definition in its configuration files, claiming that any method that creates a bean can be configured to be a Factory Method. While this colloquial definition of the Factory pattern is important, it is only a special case of the true pattern. According to the Gang of Four book that defined the technique, the intent of the Factory Method pattern is as follows:
Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.
As we can see in this textbook definition, a Factory Method involves more than just a single method. Instead, it involves creating two sets of interfaces: (1) the product interface that constitutes the object to be created and (2) the creator interface that constitutes the object that will instantiate the product. The Unified Modeling Language (UML) class diagram for this relationship is illustrated in the figure below:
Using the general pattern, we build a Creator
interface, which includes one or more Factory Methods, as well as any number of other methods. This interface may also be an abstract class with defined methods or even a default implementation of the Factory Method that returns a default instance of Product
. From this interface, a set of ConcreteCreator
classes are created that return desired ConcreteProduct
instances that implement the Product
interface.
While the basic Factory Method pattern is simple, there are some variations that can add complexity to a system. For example, the operations associated with the Creator
interface or abstract class may be Template Methods, where an algorithm is defined in terms of one or a series of Factory Methods that create objects utilized in the Template Method.
The Creator
interface may also be a full-fledged class, containing concrete implementations for each of the operations and Factory Methods. Instead of creating a full abstraction in the form of an interface (or partial abstraction in the form of an abstract class), we allow for a hierarchy that does not necessarily include any ConcreteCreator
instances. Likewise, the Product
interface may actually be a class, where the fields of the class are initialized differently depending on the ConcreteCreator
implementation that returned the Product
.
An Example in Java
Before proceeding to gain an intuitive understanding of the Factory Method pattern, we will walk through an example of the pattern in Java. In this example, we will create an encryption mechanism that allows for the user to supply a string of text and a file name, which is then encrypted and written to disk. Since there are various encryption algorithms that may be desired in a variety of scenarios, we will use the Factory Method pattern to allow for multiple encryption implementations to be created. The UML class diagram for this example is illustrated in the figure below:
We start by developing our Product
interface since it has the most dependencies (the Creator
interface directly depends on it and the ConcreteProduct
implementations inherit from it):
public interface EncryptionAlgorithm {
public String encrypt(String plaintext);
}
Next, we develop the Creator
interface. In our case, we will make our Creator
an abstract class that contains an operation that allows a client to supply the plaintext to be encrypted, as well as the file name that the encrypted cyphertext should be saved to:
public abstract class Encryptor {
public void writeToDisk(String plaintext, String filename) {
EncryptionAlgorithm encryptionAlgorithm = getEncryptionAlgorithm();
String cyphertext = encryptionAlgorithm.encrypt(plaintext);
try (FileOutputStream outputStream = new FileOutputStream(filename)) {
outputStream.write(cyphertext.getBytes());
}
catch (IOException e) {
e.printStackTrace();
}
}
public abstract EncryptionAlgorithm getEncryptionAlgorithm();
}
With both of the abstractions in place, we move onto creating the ConcreteProduct
implementations (since the ConcreteCreator
instances will depend on these implementations). In our example, we will create implementations for two encryption algorithms: (1) SHA-256 and (2) SHA-512. For both implementations, we will use the Apache Commons encryption implementations (org.apache.commons.codec.digest.DigestUtils
). The implementation of our SHA-256 encryption algorithm is as follows:
public class Sha256CEncryptionAlgorithm implements EncryptionAlgorithm {
@Override
public String encrypt(String plaintext) {
return DigestUtils.sha256Hex(plaintext);
}
}
Likewise, the implementation of our SHA-512 algorithm is as follows:
public class Sha512EncryptionAlgorithm implements EncryptionAlgorithm {
@Override
public String encrypt(String plaintext) {
return DigestUtils.sha512Hex(plaintext);
}
}
With our ConcreteProduct
implementations complete, we can now create our two corresponding ConcreteCreator
implementations: (1) Sha256Encryptor
and (2) Sha512Encryptor
. The implementation for the former is as follows:
public class Sha256Encryptor extends Encryptor {
@Override
public EncryptionAlgorithm getEncryptionAlgorithm() {
return new Sha256CEncryptionAlgorithm();
}
}
The implementation for the latter is as follows:
public class Sha512Encryptor extends Encryptor {
@Override
public EncryptionAlgorithm getEncryptionAlgorithm() {
return new Sha512EncryptionAlgorithm();
}
}
We can now supply our selected encryptor to a client and have the behavior of our system vary depending on the concrete type used. For example, suppose we create a new PersistedFile
class that consumes the contents of a file (and maybe performs some formatting), encrypts the contents, and persists it to disk. In this case, we can vary the runtime behavior of this class by supplying a concrete implementation of Encryptor
:
public class PersistedFile {
private final String path;
private final String contents;
private final Encryptor encryptor;
public PersistedFile(String path, String contents, Encryptor encryptor) {
this.path = path;
this.contents = contents;
this.encryptor = encryptor;
}
public void persist() {
encryptor.writeToDisk(contents, path);
}
}
PersistedFile file = new PersistedFile("/foo/bar/text.txt", "Hello, world!", new Sha256Encryptor());
We can vary encrypted contents of the file by simply changing the Encryptor
object supplied to the constructor of PersistedFile
. In the case above, we used our SHA-256 implementation, which will result in SHA-256 cyphertext being persisted to disk. If instead, we selected our SHA-512 implementation, we would have had SHA-256 cyphertext written to disk. By simply varying the Creator
implementation supplied to our client, we can entirely change the behavior of our system, without tieing the client to any particular implementation of our ConcreteProduct
subclasses.
Idiosyncrasies
Even with the ardent simplicity of this pattern, there are still a few nuances that must be discussed. First, we will look into simplifications of the Factory Method pattern and possible techniques for collapsing simplistic hierarchies into fewer classes. Second, we will elaborate on the inherent relationship between many Factory Method pattern implementations and the Template Method pattern.
Simplified Hierarchies
In practice, Factory Methods are non-trivial and may even include a dense degree of code to support the features provided by the pattern implementation. For example, suppose we have a User Interface (UI) framework that desires to create various visual components, such as buttons, text boxes, titles, etc. In this scenario, we may have a Product
interface, VisualComponent
, that includes methods for altering the height and width of the various components:
public interface VisualComponent {
public void setHeight(int pixels);
public void setWidth(int pixels);
}
Although this interface is very simple, the implementations that accompany it will likely be very complex. For example, if a button implementation desires to keep the same aspect ratio (constrained) when resized (if the height of the button is changed, the width will also change to ensure the same ratio of height to width is maintained), a decent level of computation may be involved. On the other hand, if the components are unconstrained, the logic involved may be simpler, but will likely require some geometric computation (i.e. if the width of a button is changed, where should the text be repositioned to ensure it is centered?).
In order to allow the user to decide on the logic involved in resizing a visual component, the framework may request that a Creator
is supplied that can provide the required components (note that this implementation contains more than one Factory Method):
public interface Button extends VisualComponent {
public void setText(String text);
}
public interface Textbox extends VisualComponent {
public void setText(String text);
public void setCharacterLimit(int limit);
}
public interface VisualComponentFactory {
public Button createButton();
public Textbox createTextbox();
}
We can then implement a ConcreteCreator
for constrained visual components:
public class ConstrainedButton implements Button { ... }
public class ConstrainedTextbox implements Textbox { ... }
public class ConstrainedVisualComponentFactory implements VisualComponentFactory {
@Override
public Button createButton() {
return new ConstrainedButton();
}
@Override
public Textbox createTextbox() {
return new ConstrainedTextbox();
}
}
Likewise, we can implement one for unconstrained visual components:
public class UnconstrainedButton implements Button { ... }
public class UnconstrainedTextbox implements Textbox { ... }
public class UnconstrainedVisualComponentFactory implements VisualComponentFactory {
@Override
public Button createButton() {
return new UnconstrainedButton();
}
@Override
public Textbox createTextbox() {
return new UnconstrainedTextbox();
}
}
We can then supply our desired Creator
to the UI framework in the following manner:
public UiBootstrapper {
private VisualComponentFactory factory;
public void setVisualComponentFactory(VisualComponentFactory factory) {
this.factory = factory;
}
public void drawUi() {
// Create a button and do something with it...
Button button = factory.createButton();
// Create a textbox and do something with it...
Textbox textbox = factory.createTextbox();
}
UiBootstrapper bootstrapper = new UiBootstrapper();
bootstrapper.setVisualComponentFactory(new ConstrainedVisualComponentFactory());
bootstrapper.drawUi();
In this scenario, the complexity of our ConcreteProduct
implementations, along with the multiplicity of our Factory Methods, warrants the creation of separate Creator
and Product
hierarchies. In the case of our encryption system above, this separation is arguably unneeded. If we reexamine the Encryptor
abstract class, it contains only one concrete method (writeToDisk
) and one factory method (getEncryptionAlgorithm
). Whatsmore, the Product
interface (EncryptionAlgorithm
) contains only a single operation (encrypt
). Due to the simplicity of this hierarchy, we may be able to condense our system by removing the Creator
from the picture. Instead, we can simply have a set of EncryptionAlgorithm
implementations and refactor our code to resemble the Strategy Pattern.
To do this, we can inline the writeToDisk
method to the PersistedFile
class as such:
public class PersistedFile {
private final String path;
private final String contents;
private final Encryptor encryptor;
public PersistedFile(String path, String contents, Encryptor encryptor) {
this.path = path;
this.contents = contents;
this.encryptor = encryptor;
}
public void persist() {
EncryptionAlgorithm encryptionAlgorithm = encryptor.getEncryptionAlgorithm();
String cyphertext = encryptionAlgorithm.encrypt(contents);
try (FileOutputStream outputStream = new FileOutputStream(path)) {
outputStream.write(cyphertext.getBytes());
}
catch (IOException e) {
e.printStackTrace();
}
}
}
PersistedFile file = new PersistedFile("/foo/bar/text.txt", "Hello, world!", new Sha256Encryptor());
This leaves us with an Encryptor
abstract class that contains only one abstract method:
public abstract class Encryptor {
public abstract EncryptionAlgorithm getEncryptionAlgorithm();
}
This abstract class simply returns a specific EncryptionAlgorithm
instance. If we look into the EncryptionAlgorithm
interface, we are left with a single method as well (encrypt
). Since each Encryptor
implementation has a one-to-one correspondence with each EncryptionAlgorithm
implementation, the indirection provided by the Encryptor
interface is not very useful: We could just as easily supply an EncryptionAlgorithm
implementation directly when an Encryptor
is expected (and we would save the overhead of the indirect call to getEncryptionAlgorithm
).
Seen more concretely, if we examine the persist
method of the PersistedFile
class, we see that the only call that we make to the Encryptor
object is getEncryptionAlgorithm
. We then call the encryption algorithm to convert our plaintext to cyphertext. We can refactor these two calls into a single call as follows (this is an intermediary refactor and may not be a good idea if refactor is intended to be the final result, as discussed here):
public class PersistedFile {
// Other fields and methods removed for clarity
public void persist() {
String cyphertext = encryptor.getEncryptionAlgorithm().encrypt(contents);
try (FileOutputStream outputStream = new FileOutputStream(path)) {
outputStream.write(cyphertext.getBytes());
}
catch (IOException e) {
e.printStackTrace();
}
}
}
We can then refactor the PersistedFile
to skip the use of the Encryptor
interface all together by dealing with an EncryptionAlgorithm
object directly:
public class PersistedFile {
private final String path;
private final String contents;
private final EncryptionAlgorithm cipher;
public PersistedFile(String path, String contents, EncryptionAlgorithm cipher) {
this.path = path;
this.contents = contents;
this.cipher = cipher;
}
public void persist() {
String cyphertext = cipher.encrypt(contents);
try (FileOutputStream outputStream = new FileOutputStream(path)) {
outputStream.write(cyphertext.getBytes());
}
catch (IOException e) {
e.printStackTrace();
}
}
}
PersistedFile file = new PersistedFile("/foo/bar/text.txt", "Hello, world!", new Sha256EncryptionAlgorithm());
Note that we now provide an EncryptionAlgorithm
object (Sha256EncryptionAlgorithm
rather than Sha256Encryptor
) in the constructor of PersistedFile
. At the completion of this refactor, we have transformed our Factory Method pattern implementation into a Strategy Pattern implementation, where each EncryptionAlgorithm
subclass represents a concrete strategy. This transformation may not be appropriate in all cases and judgment should be used when deciding if a Factory Method pattern implementation is too simple for separate Creator
and Product
hierarchies. In the event that the Factory Method implementation is over-simplified, the Strategy Pattern may prove to be a useful alternative.
Template Methods
As seen with the Strategy Pattern alternative, multiple patterns are often interrelated. In the case of the Factory Method pattern, the Template Method pattern is often found not far away. In the Template Method pattern, we create a method that acts as an algorithm, where other abstract methods are used as the steps. For example, we can create a template for handling different events when parsing eXtensible Markup Language (XML) snippets, such as:
<books>
<book>
<title>The Great Divorce</title>
<author>C.S. Lewis</author>
</book>
<book>
<title>The Devil in the White City</title>
<author>Erik Larson</author>
</book>
</books>
The Template Method implementation for the handler may resemble:
public class Element {
private final String tag;
public Element(String tag) {
this.tag = tag;
}
public String getTag() {
return tag;
}
}
public abstract class XmlHandler {
public void parse(String xmlSnippet) {
// ...parse the XML file...
// ...when XML element is found, create Element object and call...
Element element = getNextElement();
handleElement(element);
}
public abstract void handleElement(Element element);
}
We can then create an implementation of the handler:
public class XmlPrintHandler extends XmlHandler {
@Override
public void handleElement(Element element) {
System.out.println("Found element with tag: " + element.getTag());
}
}
We can then use our handler to parse some XML:
XmlHandler handler = new XmlPrintHandler();
handler.parse("<books><book><title>Ben-Hur</title><author>Lew Wallace</author></book></books>");
This would result in the following output:
Found element with tag: books
Found element with tag: book
Found element with tag: title
Found element with tag: author
The interrelationship with the Factory Method pattern is introduced when we make our step methods (such as handleElement
) Factory Methods. For example, we can create a Product
hierarchy to handle the found XML elements:
public interface ElementHandler {
public void handleElement(Element element);
}
public class PrintElementHandler implements ElementHandler {
@Override
public void handleElement(Element element) {
System.out.println("Found element with tag: " + element.getTag());
}
}
public class LoggerElementHandler implements ElementHandler {
@Override
public void handleElement(Element element) {
// ...log the element tag...
}
}
We can then replace the original step method (XmlHandler::handleElement
) with a Factory Method that returns the ElementHandler
:
public abstract class XmlHandler {
public void parse(String xmlSnippet) {
// ...parse the XML file...
// ...when XML element is found, create Element object and call...
Element element = getNextElement();
ElementHandler handler = getElementHandler();
handler.handleElement(element);
}
public abstract ElementHandler getElementHandler();
}
We can then refactor our XmlPrintHandler
to conform to this XmlHandler
abstract class:
public class XmlPrintHandler extends XmlHandler {
@Override
public ElementHandler getElementHandler() {
return new PrintElementHandler();
}
}
The parser now functions exactly as it once did, but we have introduced a Factory Method into the XmlHandler
abstract class. With this introduction, we have made XmlHandler
the Creator
, XmlPrintHandler
the ConcreteCreator
, ElementHandler
the Product
, and PrintElementHandler
the ConcreteProduct
. Our Creator
interface may be expanded later to include handlers for open and closing elements, rather than just general elements (in our case, implicitly meaning opening elements). While the template implementation is simpler, introducing Factory Methods may provide useful functionality.
For example, suppose we do not care about the ordering of processed elements (i.e. we only wish to count how many books are in a given XML snippet by counting the number of opening <book>
elements), we can parallelize this parsing process, creating a new ElementHandler
each time a new element is encountered. In the template technique, multiple-threading may be difficult to achieve, since a single method on a single object is being called by multiple threads. In the Factory Method case, we create a new object for each occurrence of an element, thus removing the possibility of contention between invocations of the handleElement
method (each invocation is executed on a new instance of ElementHandler
rather than multiple invocations on a single XmlHandler
instance).
Colloquial Definition
While we have explored the textbook definition of the Factory Method pattern from the Gang of Four Design Patterns book, this is often not how the term Factory pattern (note that the word method is often dropped) is used by developers. In many cases, the following definition is implicitly used:
A method whose sole responsibility is to abstract the creation process of an object and return that object.
Many times, this definition is implemented in the form of a static method that returns an object, but whose return type is an interface for that object. For example:
public class XmlHandlerFactory {
public static XmlHandler createHandler() {
return new XmlPrintHandler();
}
}
This simplified definition of the Factory Method pattern is a special case of the general pattern, where the Creator
hierarchy is reduced to a single class and the factory method is reduced to a single static method (rather than a method that is overridden for each of the possible Product
implementation can be returned). In essence, the Creator
returns a default Product
instance and no other ConcreteCreator
can override this default.
This pattern is often used as an implementation of the Facade pattern when dealing with external libraries or projects that externally compiled. The use of this static method allows for a single point of entry into a library. For example, if we packaged our XML handling logic into a project or module, we could allow for external clients to obtain the selected handler functionality in the following manner:
XmlHandler handler = XmlHandlerFactory.createHandler();
This structure assumes that the XmlHandler
Factory has enough knowledge to know which handler implementation is appropriate. If the factory does not have sufficient information to make this decision, it may be a wise idea to provide various implementations and allow the client to select an appropriate implementation, possibly through dependency injection. This allows the client to select an existing implementation and use it if the implementation suffices or create a new implementation if no existing implementation suffices.
Conclusion
Patterns can be an excellent asset in solving recurring problems, but they can also introduce a great deal of confusion if not properly understood. One of the most commonly misunderstood patterns is the Factory Method pattern, which is commonly mistaken for a simplification of the original pattern. In this article, we explored the purpose of the pattern, walked through an example of the pattern in Java, and examined some other patterns that can be good alternatives to the Factory Method pattern. We also explored the simplified version of this pattern that is used in daily practice. Although this pattern is more complex than the simplified version, it is important to understand the general case in order to understand when and where the Factory Method pattern is appropriate in a design.
Opinions expressed by DZone contributors are their own.
Comments