Table of Contents
- What is the Factory Method Design Pattern?
- Key Components of Factory Method
- When to Use the Factory Method Pattern
- Real-World Analogy
- Hands-on Implementation in Python
- Advantages and Disadvantages
- Comparison with Other Patterns
- Conclusion
- References
What is the Factory Method Design Pattern?
The Factory Method Design Pattern is a creational design pattern that defines an interface (or abstract class) for creating objects but delegates the actual instantiation to its subclasses. In other words, it defers object creation to child classes, allowing them to decide which class to instantiate.
The pattern is formally defined in the Gang of Four (GoF) book as:
“Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.”
At its core, the Factory Method pattern decouples the client code from the specific classes it needs to instantiate. Instead of directly creating objects (e.g., WordDocument()), the client relies on a “factory” to handle object creation, making the code more flexible and easier to extend.
Key Components of Factory Method
To implement the Factory Method pattern, you’ll need four main components:
1. Product
- An interface (or abstract class) defining the common methods that all concrete products must implement.
- Represents the objects the factory method creates.
2. Concrete Product
- A concrete class that implements the
Productinterface. - These are the actual objects created by the factory method.
3. Creator
- An abstract class (or interface) that declares the factory method (a method responsible for creating
Productobjects). - The factory method is typically abstract, forcing subclasses to implement it. The creator may also provide a default implementation of the factory method.
4. Concrete Creator
- A concrete class that extends the
Creatorand overrides the factory method to return an instance of aConcrete Product.
When to Use Factory Method
Use the Factory Method pattern when:
- A class cannot anticipate the type of objects it needs to create at runtime.
- A class wants its subclasses to specify the objects it creates.
- You want to localize the logic for creating objects (adheres to the Single Responsibility Principle).
- You need to enforce the Open/Closed Principle (easily add new product types without modifying existing client code).
Real-World Analogy
Think of a furniture manufacturing company. The company (Creator) has a factory that produces furniture (Product). However, the factory doesn’t decide whether to make chairs, tables, or sofas—it delegates that decision to specialized departments (Concrete Creators):
- Creator: The main factory (abstract class with a
create_furniture()method). - Concrete Creators: Chair Department, Table Department (subclasses that override
create_furniture()to make chairs or tables). - Product: Furniture (interface with methods like
assemble(),paint()). - Concrete Products: Chair, Table (implement
Furnitureand define specific assembly/painting logic).
This way, the main factory doesn’t need to know how to make each furniture type; the specialized departments handle that. Adding a new product (e.g., Sofa) only requires a new SofaDepartment (Concrete Creator) and Sofa (Concrete Product)—no changes to the main factory.
Hands-on Implementation in Python
Let’s implement the Factory Method pattern with a practical example. We’ll build a document editor that can create and manage different types of documents (e.g., Word, PDF).
Problem Statement
Suppose we want an editor that can open and save documents. Without the Factory Method pattern, the client code might directly instantiate WordDocument or PdfDocument based on user input, leading to tight coupling and conditional logic bloat.
Without Factory Method: The Problem
Here’s how the code might look without the Factory Method pattern:
class WordDocument:
def open(self):
print("Opening Word document...")
def save(self):
print("Saving Word document...")
class PdfDocument:
def open(self):
print("Opening PDF document...")
def save(self):
print("Saving PDF document...")
# Client code
def main():
document_type = input("Enter document type (word/pdf): ").lower()
# Conditional logic to create documents (problematic!)
if document_type == "word":
doc = WordDocument()
elif document_type == "pdf":
doc = PdfDocument()
else:
raise ValueError("Unsupported document type")
doc.open()
doc.save()
if __name__ == "__main__":
main()
Issues with this approach:
- The client code is tightly coupled to
WordDocumentandPdfDocument. - Adding a new document type (e.g., Excel) requires modifying the client’s conditional logic (violates Open/Closed Principle).
- The client handles object creation, mixing creation logic with business logic (violates Single Responsibility Principle).
With Factory Method: The Solution
Let’s refactor this using the Factory Method pattern to address these issues.
Step 1: Define the Product Interface
First, create an abstract Document class (Product) with common methods (open(), save()).
from abc import ABC, abstractmethod
class Document(ABC):
@abstractmethod
def open(self):
pass
@abstractmethod
def save(self):
pass
Step 2: Implement Concrete Products
Next, create concrete document types that implement the Document interface.
class WordDocument(Document):
def open(self):
print("Opening Word document...")
def save(self):
print("Saving Word document...")
class PdfDocument(Document):
def open(self):
print("Opening PDF document...")
def save(self):
print("Saving PDF document...")
# Add a new product later (e.g., ExcelDocument) without changing existing code!
class ExcelDocument(Document):
def open(self):
print("Opening Excel document...")
def save(self):
print("Saving Excel document...")
Step 3: Define the Creator (Abstract Factory)
Create an abstract DocumentEditor class (Creator) with a factory method create_document(). This method will be overridden by subclasses to create specific documents.
class DocumentEditor(ABC):
@abstractmethod
def create_document(self) -> Document:
"""Factory method: Subclasses must implement this to create a Document."""
pass
def edit_document(self):
"""Client method that uses the factory method to create and work with a Document."""
document = self.create_document()
document.open()
document.save()
Step 4: Implement Concrete Creators
Create subclasses of DocumentEditor (Concrete Creators) that override create_document() to return specific Document instances.
class WordEditor(DocumentEditor):
def create_document(self) -> Document:
return WordDocument()
class PdfEditor(DocumentEditor):
def create_document(self) -> Document:
return PdfDocument()
class ExcelEditor(DocumentEditor):
def create_document(self) -> Document:
return ExcelDocument()
Step 5: Client Code
Now, the client code uses the DocumentEditor subclasses instead of directly instantiating documents. Adding a new document type only requires a new Concrete Creator and Concrete Product.
def main():
editor_type = input("Enter editor type (word/pdf/excel): ").lower()
# Choose the appropriate editor (Concrete Creator)
if editor_type == "word":
editor = WordEditor()
elif editor_type == "pdf":
editor = PdfEditor()
elif editor_type == "excel":
editor = ExcelEditor()
else:
raise ValueError("Unsupported editor type")
# Edit the document (client doesn't directly create the Document!)
editor.edit_document()
if __name__ == "__main__":
main()
How It Works
- The client code selects an
editor(Concrete Creator) based on input. - The editor calls
create_document()(factory method) to instantiate the appropriateDocument(Concrete Product). - The client never directly creates a
Document—it delegates creation to the editor.
Advantages and Disadvantages
Advantages
- Loose Coupling: Client code is decoupled from concrete product classes.
- Open/Closed Principle: Add new products by creating new
Concrete ProductandConcrete Creatorclasses (no changes to existing code). - Single Responsibility Principle: Object creation logic is isolated in factory methods.
- Flexibility: Subclasses control the type of objects created, making the code more adaptable.
Disadvantages
- Increased Complexity: Requires creating multiple new classes (abstract products, concrete products, creators), which can overcomplicate simple scenarios.
- Overkill for Simple Cases: If you only have one product type, the pattern adds unnecessary layers.
Comparison with Other Patterns
Factory Method vs. Simple Factory
- Simple Factory: A concrete class with a static method that creates objects based on input (e.g.,
DocumentFactory.create_document("word")). It does not use inheritance—all logic is in one class. - Factory Method: Uses inheritance; subclasses override the factory method to create objects. More flexible for extension.
Factory Method vs. Abstract Factory
- Factory Method: Creates one type of product (e.g., documents).
- Abstract Factory: Creates families of related products (e.g., documents + document templates + export tools).
Conclusion
The Factory Method Design Pattern is a powerful tool for managing object creation in a flexible, maintainable way. By delegating instantiation to subclasses, it promotes loose coupling, adheres to design principles like Open/Closed and Single Responsibility, and makes it easy to extend your code with new product types.
Use it when you need to decouple object creation from client code or when subclasses should control the type of objects created. Avoid it for simple scenarios where the overhead of extra classes isn’t justified.
References
- Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.
- Refactoring.Guru: Factory Method
- Real Python: Factory Method
- Python ABC Module Documentation