How to Implement Abstract Factory Design Pattern in Python
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:
# Without pattern - messy and hard to maintain
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
# Abstract Products
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
# Concrete Products - Windows Family
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"
# Concrete Products - macOS Family
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"
# Abstract Factory
class GUIFactory(ABC):
@abstractmethod
def create_button(self) -> Button:
pass
@abstractmethod
def create_checkbox(self) -> Checkbox:
pass
# Concrete Factories
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()
# Client Code - completely decoupled from concrete classes
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())
# Usage
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 toggled
Factory 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()
# Application bootstrap
factory = get_factory()
create_ui(factory)
The factory selection happens once at application startup. All subsequent code uses the abstract factory interface, remaining unaware of which concrete factory is in use.
Extended Example: Database Connections
A practical example with database components:
from abc import ABC, abstractmethod
# Abstract Products
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
# PostgreSQL Family
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)"
# MySQL Family
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)"
# Abstract Factory
class DatabaseFactory(ABC):
@abstractmethod
def create_connection(self) -> Connection:
pass
@abstractmethod
def create_query(self) -> Query:
pass
@abstractmethod
def create_transaction(self) -> Transaction:
pass
# Concrete Factories
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()
# Client code works with any database
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
# New family - only additions, no modifications
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()
# Existing client code works unchanged
create_ui(LinuxFactory())
Adding a new product type (e.g., Slider) requires modifying the abstract factory and all concrete factories. This is the trade-off of the pattern.
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.