Factory Method Pattern

Chirag Vaswani
4 min readMay 29, 2024

--

THE WHAT

The Factory Method is a way to create objects without specifying their exact class (or using the new operator in the client). Instead, you have a base class with a method for creating objects, and subclasses of this base class decide what specific type of object to create. This makes it easy to choose or switch the type of object being created without changing the main code that uses them.

In other words…

The Factory Method is like a tool that helps you make different types of products. You have a main tool (base class) that knows how to make a product, but the details of what specific product to make are decided by different versions of the tool (subclasses). This way, you can add new types of products without changing the main tool.

The General Problem it tries to solve

The Factory Method addresses the scenarios where the exact type of object isn’t known until runtime, or when the creation process is complex and should be centralized to manage changes more effectively.

Example of the General Problem

Consider a software system that manages various types of documents. Initially, it supports text documents and spreadsheets. As the system evolves, new document types, such as presentations, are added. Hardcoding the creation logic for each document type within the client code leads to tight coupling and makes the system difficult to maintain and extend. The Factory Method provides a way to decouple the client code from the specific classes it instantiates, allowing new document types to be added with minimal changes to the existing code.

THE WHEN

You should consider using the Factory Method when:

  • You don’t know beforehand the exact types and dependencies of the objects your code should work with.
  • A Class Wants Its Subclasses to Specify the Objects It Creates
  • Classes Delegate Responsibility to One of Several Helper Subclasses, and You Want to Localize the Knowledge of Which Helper Subclass is the Delegate

THE HOW

  1. Define a Product Interface: Ensure all products follow the same interface, which declares methods that make sense for every product.
public interface Document {
void open();
void close();
void save();
}

2. Create Concrete Products: Implement different versions of the product interface.

public class TextDocument implements Document {
@Override
public void open() { /* ... */ }
@Override
public void close() { /* ... */ }
@Override
public void save() { /* ... */ }
}

public class SpreadsheetDocument implements Document {
@Override
public void open() { /* ... */ }
@Override
public void close() { /* ... */ }
@Override
public void save() { /* ... */ }
}

public class PresentationDocument implements Document {
@Override
public void open() { /* ... */ }
@Override
public void close() { /* ... */ }
@Override
public void save() { /* ... */ }
}

3. Define a Creator Class: Declare a factory method that returns new product objects. The return type of this method should match the product interface.

public abstract class DocumentViewer {
// Factory method
public abstract Document createDocument();

public void openDocument() {
Document doc = createDocument();
doc.open();
// additional operations
}

public void closeDocument() {
Document doc = createDocument();
doc.close();
// additional operations
}

public void saveDocument() {
Document doc = createDocument();
doc.save();
// additional operations
}
}

4. Create Concrete Creators: Subclass the creator class and override the factory method to return different types of products.

public class TextDocumentViewer extends DocumentViewer {
@Override
public Document createDocument() {
return new TextDocument();
}
}

public class SpreadDocumentViewer extends DocumentViewer {
@Override
public Document createDocument() {
return new SpreadsheetDocument();
}
}

public class PresentationDocumentViewer extends DocumentViewer {
@Override
public Document createDocument() {
return new PresentationDocument();
}
}

5. Create a Factory Class: Create a factory class to bind it all together.

public class DocumentViewerFactory {
public static DocumentViewer getDocumentViewer(String type) {
switch (type.toLowerCase()) {
case "text":
return new TextDocumentViewer();
case "spreadsheet":
return new SpreadsheetDocumentViewer();
case "presentation":
return new PresentationDocumentViewer();
default:
throw new IllegalArgumentException("Unknown document type: " + type);
}
}
}
public class Main {
public static void main(String[] args) {
// Example usage of the factory
DocumentViewer textViewer = DocumentViewerFactory.getDocumentViewer("text");
textViewer.openDocument();
textViewer.saveDocument();
textViewer.closeDocument();

DocumentViewer spreadsheetViewer = DocumentViewerFactory.getDocumentViewer("spreadsheet");
spreadsheetViewer.openDocument();
spreadsheetViewer.saveDocument();
spreadsheetViewer.closeDocument();

DocumentViewer presentationViewer = DocumentViewerFactory.getDocumentViewer("presentation");
presentationViewer.openDocument();
presentationViewer.saveDocument();
presentationViewer.closeDocument();
}
}

UML Diagram

GENERIC UML DIAGRAM

THE CONSEQUENCES

Pros

  1. Avoids Tight Coupling: The creator is decoupled from the concrete products it creates.
  2. Single Responsibility Principle: Product creation code is centralized, making it easier to manage and maintain.
  3. Open/Closed Principle: New product types can be added without modifying existing client code.

Cons

  1. Increased Complexity: The pattern introduces additional classes and subclasses, which can complicate the codebase.
  2. Proliferation of Subclasses: For each new product type, a new subclass of the creator is typically needed.

The Tradeoffs

While the Factory Method pattern provides flexibility and decouples the client code from specific classes, it also introduces additional complexity. This complexity is a tradeoff for the maintainability and extensibility gains. Therefore, it's crucial to evaluate whether the benefits outweigh the costs in your particular context.

Conclusion

The Factory Method pattern is a powerful tool for creating objects in a flexible and maintainable way. By encapsulating the object creation process, it adheres to key design principles, making it easier to extend and manage code. However, it's essential to balance its benefits against the complexity it introduces, ensuring that its use adds value to your project.

Thank you

--

--