Skip to content

How to Implement Abstract Factory Design Pattern in Python

Learn how to create families of related objects in Python using the Abstract Factory pattern, with practical examples for UI toolkits and database components.

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 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()

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:

  1. Create concrete products implementing existing abstract products
  2. Create a new concrete factory
  3. 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.

Navigation