The Abstract Factory is a creational pattern that provides an interface for creating families of related objects without specifying their concrete classes. It ensures that products from the same family are used together while keeping client code independent of specific implementations.
The Problem
Consider building a cross-platform UI toolkit with buttons and checkboxes. Without a pattern, you'd scatter platform checks throughout your codebase:
if platform == "windows":
button = WinButton()
checkbox = WinCheckbox()
elif platform == "mac":
button = MacButton()
checkbox = MacCheckbox()The Abstract Factory centralizes this decision and ensures consistency.
Implementation
from abc import ABC, abstractmethod
class Button(ABC):
@abstractmethod
def render(self) -> str:
pass
@abstractmethod
def click(self) -> str:
pass
class Checkbox(ABC):
@abstractmethod
def render(self) -> str:
pass
@abstractmethod
def toggle(self) -> str:
pass
class WindowsButton(Button):
def render(self) -> str:
return "[Windows Button]"
def click(self) -> str:
return "Windows button clicked"
class WindowsCheckbox(Checkbox):
def render(self) -> str:
return "[Windows Checkbox]"
def toggle(self) -> str:
return "Windows checkbox toggled"
class MacButton(Button):
def render(self) -> str:
return "(Mac Button)"
def click(self) -> str:
return "Mac button clicked"
class MacCheckbox(Checkbox):
def render(self) -> str:
return "(Mac Checkbox)"
def toggle(self) -> str:
return "Mac checkbox toggled"
class GUIFactory(ABC):
@abstractmethod
def create_button(self) -> Button:
pass
@abstractmethod
def create_checkbox(self) -> Checkbox:
pass
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()
def create_ui(factory: GUIFactory):
button = factory.create_button()
checkbox = factory.create_checkbox()
print(button.render())
print(checkbox.render())
print(button.click())
print(checkbox.toggle())
print("Windows UI:")
create_ui(WindowsFactory())
print("\nMac UI:")
create_ui(MacFactory())Output:
Windows UI:
[Windows Button]
[Windows Checkbox]
Windows button clicked
Windows checkbox toggled
Mac UI:
(Mac Button)
(Mac Checkbox)
Mac button clicked
Mac checkbox toggledFactory Selection
Centralize the factory selection logic:
import platform
def get_factory() -> GUIFactory:
"""Return appropriate factory based on current platform."""
system = platform.system()
factories = {
"Windows": WindowsFactory,
"Darwin": MacFactory, # macOS
}
factory_class = factories.get(system, WindowsFactory)
return factory_class()
factory = get_factory()
create_ui(factory)Extended Example: Database Connections
A practical example with database components:
from abc import ABC, abstractmethod
class Connection(ABC):
@abstractmethod
def connect(self) -> str:
pass
class Query(ABC):
@abstractmethod
def execute(self, sql: str) -> str:
pass
class Transaction(ABC):
@abstractmethod
def begin(self) -> str:
pass
@abstractmethod
def commit(self) -> str:
pass
class PostgresConnection(Connection):
def connect(self) -> str:
return "Connected to PostgreSQL"
class PostgresQuery(Query):
def execute(self, sql: str) -> str:
return f"PostgreSQL executing: {sql}"
class PostgresTransaction(Transaction):
def begin(self) -> str:
return "BEGIN TRANSACTION (PostgreSQL)"
def commit(self) -> str:
return "COMMIT (PostgreSQL)"
class MySQLConnection(Connection):
def connect(self) -> str:
return "Connected to MySQL"
class MySQLQuery(Query):
def execute(self, sql: str) -> str:
return f"MySQL executing: {sql}"
class MySQLTransaction(Transaction):
def begin(self) -> str:
return "START TRANSACTION (MySQL)"
def commit(self) -> str:
return "COMMIT (MySQL)"
class DatabaseFactory(ABC):
@abstractmethod
def create_connection(self) -> Connection:
pass
@abstractmethod
def create_query(self) -> Query:
pass
@abstractmethod
def create_transaction(self) -> Transaction:
pass
class PostgresFactory(DatabaseFactory):
def create_connection(self) -> Connection:
return PostgresConnection()
def create_query(self) -> Query:
return PostgresQuery()
def create_transaction(self) -> Transaction:
return PostgresTransaction()
class MySQLFactory(DatabaseFactory):
def create_connection(self) -> Connection:
return MySQLConnection()
def create_query(self) -> Query:
return MySQLQuery()
def create_transaction(self) -> Transaction:
return MySQLTransaction()
def run_migration(factory: DatabaseFactory):
conn = factory.create_connection()
query = factory.create_query()
tx = factory.create_transaction()
print(conn.connect())
print(tx.begin())
print(query.execute("CREATE TABLE users (id INT)"))
print(tx.commit())Adding New Product Families
Adding a new family (e.g., Linux UI) requires:
- Create concrete products implementing existing abstract products
- Create a new concrete factory
- No changes to client code
class LinuxButton(Button):
def render(self) -> str:
return "<Linux Button>"
def click(self) -> str:
return "Linux button clicked"
class LinuxCheckbox(Checkbox):
def render(self) -> str:
return "<Linux Checkbox>"
def toggle(self) -> str:
return "Linux checkbox toggled"
class LinuxFactory(GUIFactory):
def create_button(self) -> Button:
return LinuxButton()
def create_checkbox(self) -> Checkbox:
return LinuxCheckbox()
create_ui(LinuxFactory())Pattern Components
| Component | Role | Example |
|---|---|---|
| Abstract Factory | Declares creation methods | GUIFactory |
| Concrete Factory | Implements creation for one family | WindowsFactory |
| Abstract Product | Interface for a product type | Button |
| Concrete Product | Specific implementation | WindowsButton |
| Client | Uses only abstract interfaces | create_ui() |
When to Use
- Multiple product families: Different themes, platforms, or configurations
- Family consistency required: Products from one family must work together
- Runtime family switching: Change entire product set based on configuration
- Isolating concrete classes: Hide implementation details from client code
Comparison with Factory Method
| Aspect | Factory Method | Abstract Factory |
|---|---|---|
| Creates | Single product | Family of products |
| Inheritance | Subclass per product | Subclass per family |
| Complexity | Lower | Higher |
| Use case | One varying product | Multiple related products |
The Abstract Factory pattern excels when you need to ensure that related objects are created together and remain compatible, while keeping client code independent of specific implementations.