Table of Contents
- What Are Design Patterns?
- Creational Design Patterns: A Primer
- Factory Method Pattern
- 3.1 Intent
- 3.2 Structure
- 3.3 Python Example
- Abstract Factory Pattern
- 4.1 Intent
- 4.2 Structure
- 4.3 Python Example
- Factory vs. Abstract Factory: Key Differences
- When to Use Which?
- Real-World Examples
- Common Pitfalls
- Conclusion
- References
What Are Design Patterns?
Design patterns are proven, reusable solutions to recurring problems in software design. Coined by the “Gang of Four” (GoF)—Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides—in their 1994 book Design Patterns: Elements of Reusable Object-Oriented Software, they provide a common vocabulary for developers to communicate complex design ideas.
Patterns are not code snippets but guidelines for structuring code to address specific scenarios (e.g., creating objects, managing relationships between objects, or controlling algorithm behavior).
Creational Design Patterns: A Primer
Creational patterns focus on how objects are created. They abstract the instantiation process, making a system independent of how its objects are created, composed, and represented. This flexibility is critical for building scalable applications where object creation logic might change over time.
Common creational patterns include:
- Singleton: Ensures a class has only one instance.
- Factory Method: Delegates object creation to subclasses.
- Abstract Factory: Creates families of related objects.
- Builder: Constructs complex objects step-by-step.
- Prototype: Creates new objects by cloning existing ones.
In this blog, we dive deep into Factory Method and Abstract Factory, two patterns often confused due to their similar names but distinct use cases.
Factory Method Pattern
3.1 Intent
The Factory Method pattern defines an interface for creating an object but delegates the actual instantiation to subclasses. This allows a class to defer instantiation to its subclasses, ensuring the client code remains decoupled from the specific types of objects being created.
Key Goal: To encapsulate object creation logic while allowing flexibility to add new product types without modifying existing client code.
3.2 Structure
The Factory Method pattern consists of the following components:
| Component | Role |
|---|---|
| Product | An abstract interface/abstract class defining the common interface for all products the factory method creates. |
| Concrete Product | A concrete implementation of the Product interface. |
| Creator | An abstract class/interface declaring the factory method, which returns a Product object. It may also provide a default implementation of the factory method. |
| Concrete Creator | A subclass of Creator that overrides the factory method to return an instance of a Concrete Product. |
3.3 Python Example
Let’s implement a simple document generator where we support two document types: WordDocument and PdfDocument. The Factory Method will handle creating the appropriate document type based on the input.
Step 1: Define the Product Interface
We’ll use Python’s abc module to create an abstract Document class with a generate() method.
from abc import ABC, abstractmethod
class Document(ABC):
@abstractmethod
def generate(self) -> str:
pass
Step 2: Implement Concrete Products
Next, we’ll create concrete products (WordDocument and PdfDocument) that implement Document.
class WordDocument(Document):
def generate(self) -> str:
return "Generating a Word document..."
class PdfDocument(Document):
def generate(self) -> str:
return "Generating a PDF document..."
Step 3: Define the Creator Interface
The DocumentCreator is an abstract class with a factory method create_document(). It also includes a process() method that uses the factory method to generate the document.
class DocumentCreator(ABC):
@abstractmethod
def create_document(self) -> Document:
pass
def process(self) -> str:
# The creator delegates document creation to the factory method
document = self.create_document()
return document.generate()
Step 4: Implement Concrete Creators
Concrete creators (WordDocumentCreator and PdfDocumentCreator) override create_document() to return specific product instances.
class WordDocumentCreator(DocumentCreator):
def create_document(self) -> Document:
return WordDocument()
class PdfDocumentCreator(DocumentCreator):
def create_document(self) -> Document:
return PdfDocument()
Step 5: Client Code
The client uses the creator to generate documents without directly instantiating WordDocument or PdfDocument.
def client_code(creator: DocumentCreator) -> None:
print(f"Client: Generating document...\n{creator.process()}")
# Generate a Word document
word_creator = WordDocumentCreator()
client_code(word_creator) # Output: Client: Generating document...\nGenerating a Word document...
# Generate a PDF document
pdf_creator = PdfDocumentCreator()
client_code(pdf_creator) # Output: Client: Generating document...\nGenerating a PDF document...
Key Takeaway
- The client code (
client_code) depends only on theDocumentCreatorandDocumentabstractions, not concrete implementations. - Adding a new document type (e.g.,
ExcelDocument) requires:- Creating a new
Concrete Product(e.g.,ExcelDocument). - Creating a new
Concrete Creator(e.g.,ExcelDocumentCreator).
- Creating a new
- No changes to existing client code or
Creator/Productinterfaces are needed—this adheres to the Open/Closed Principle (open for extension, closed for modification).
Abstract Factory Pattern
4.1 Intent
The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. It ensures that all products in a family are compatible and used together.
Key Goal: To enforce consistency between related products (e.g., a Windows UI toolkit should use Windows-style buttons, checkboxes, and text fields, not a mix of Windows and macOS components).
4.2 Structure
The Abstract Factory pattern includes these components:
| Component | Role |
|---|---|
| Abstract Factory | An interface/abstract class declaring a set of methods for creating each type of product in the family (e.g., create_button(), create_checkbox()). |
| Concrete Factory | A concrete implementation of Abstract Factory that creates a family of Concrete Products (e.g., WindowsFactory creates Windows-style UI components). |
| Abstract Product | An interface/abstract class for a type of product in the family (e.g., Button, Checkbox). |
| Concrete Product | A concrete implementation of an Abstract Product (e.g., WindowsButton, MacCheckbox). |
| Client | Uses only interfaces declared by Abstract Factory and Abstract Product to create and use products. |
4.3 Python Example
Let’s build a GUI toolkit with two themes: Windows and macOS. Each theme includes a Button and Checkbox, and the Abstract Factory will ensure all components in a theme are consistent.
Step 1: Define Abstract Products
We’ll create abstract Button and Checkbox classes with a render() method.
from abc import ABC, abstractmethod
class Button(ABC):
@abstractmethod
def render(self) -> str:
pass
class Checkbox(ABC):
@abstractmethod
def render(self) -> str:
pass
Step 2: Implement Concrete Products
Create Windows and macOS-specific implementations of Button and Checkbox.
class WindowsButton(Button):
def render(self) -> str:
return "Rendering a Windows-style button."
class MacButton(Button):
def render(self) -> str:
return "Rendering a macOS-style button."
class WindowsCheckbox(Checkbox):
def render(self) -> str:
return "Rendering a Windows-style checkbox."
class MacCheckbox(Checkbox):
def render(self) -> str:
return "Rendering a macOS-style checkbox."
Step 3: Define the Abstract Factory
The GUIFactory interface declares methods to create Button and Checkbox products.
class GUIFactory(ABC):
@abstractmethod
def create_button(self) -> Button:
pass
@abstractmethod
def create_checkbox(self) -> Checkbox:
pass
Step 4: Implement Concrete Factories
Create factories for Windows and macOS themes.
class WindowsFactory(GUIFactory):
def create_button(self) -> Button:
return WindowsButton()
def create_checkbox(self) -> Checkbox:
return WindowsCheckbox()
class MacFactory(GUIFactory):
def create_button(self) -> Button:
return MacButton()
def create_checkbox(self) -> Checkbox:
return MacCheckbox()
Step 5: Client Code
The client uses the factory to create and render components, ensuring consistency.
def render_gui(factory: GUIFactory) -> None:
button = factory.create_button()
checkbox = factory.create_checkbox()
print(button.render())
print(checkbox.render())
# Render Windows GUI
windows_factory = WindowsFactory()
render_gui(windows_factory)
# Output:
# Rendering a Windows-style button.
# Rendering a Windows-style checkbox.
# Render macOS GUI
mac_factory = MacFactory()
render_gui(mac_factory)
# Output:
# Rendering a macOS-style button.
# Rendering a macOS-style checkbox.
Key Takeaway
- The client code (
render_gui) is decoupled from concrete product classes (e.g.,WindowsButton). It only interacts withGUIFactory,Button, andCheckboxinterfaces. - Adding a new theme (e.g.,
Linux) requires:- Creating new
Concrete Products(e.g.,LinuxButton,LinuxCheckbox). - Creating a new
Concrete Factory(e.g.,LinuxFactory).
- Creating new
- All components in a theme are guaranteed to be compatible (no mixing Windows and macOS components).
Factory vs. Abstract Factory: Key Differences
| Feature | Factory Method | Abstract Factory |
|---|---|---|
| Purpose | Creates a single product type. | Creates families of related/dependent products. |
| Product Scope | Focuses on one product hierarchy. | Focuses on multiple product hierarchies (families). |
| Number of Factory Methods | One factory method per creator. | Multiple factory methods (one per product type in the family). |
| Implementation Complexity | Simpler (single product). | More complex (multiple products and factories). |
| Use Case | When you need to delegate object creation to subclasses. | When you need to enforce consistency between related products. |
When to Use Which?
Use Factory Method When:
- You don’t know the exact types of products your code will work with at runtime.
- You want to localize the knowledge of which class gets instantiated.
- You need to extend the product lineup by adding new
Concrete ProductsandConcrete Creators(e.g., adding a new document type to a generator).
Use Abstract Factory When:
- Your system needs to be independent of how its products are created, composed, and represented.
- Your system must use multiple families of products, and you want to ensure they are used consistently.
- You want to provide a library of products and expose only their interfaces, not implementations (e.g., a UI toolkit with themes).
Real-World Examples
Factory Method
- Logging Systems: A logger factory that creates
FileLogger,ConsoleLogger, orDatabaseLoggerbased on configuration. - Payment Gateways: A payment processor that uses a factory method to create
StripePayment,PayPalPayment, orApplePayPaymentobjects.
Abstract Factory
- Theme Engines: Tools like WordPress themes, where a theme defines fonts, colors, and UI components (all related products).
- Database Drivers: A database factory that creates
Connection,Statement, andResultSetobjects compatible with MySQL, PostgreSQL, or SQLite.
Common Pitfalls
- Overcomplicating with Abstract Factory: Using Abstract Factory for simple cases (single product) adds unnecessary complexity. Prefer Factory Method for single-product scenarios.
- Ignoring Interfaces: Failing to define clear
Product/Factoryinterfaces leads to tight coupling between client code and concrete implementations. - Overengineering: Adding patterns prematurely. Use Factory/Abstract Factory only when you anticipate future changes in product types or families.
Conclusion
Both Factory Method and Abstract Factory are powerful creational patterns, but they solve distinct problems:
- Factory Method is ideal for creating a single product type, delegating instantiation to subclasses.
- Abstract Factory shines when you need to create families of related products, ensuring consistency across components.
By understanding their differences and use cases, you can write more flexible, maintainable code that adheres to design principles like the Open/Closed Principle. Always choose the simplest pattern that meets your needs—start with Factory Method, and升级到 Abstract Factory when product families become necessary.
References
- Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.
- Python Software Foundation. (n.d.). abc — Abstract Base Classes.
- Real Python. (2020). Python Design Patterns: Factory Method.
- GeeksforGeeks. (2023). Abstract Factory Design Pattern.