The Abstract Factory is a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. Unlike the Factory Method, which creates one type of product, the Abstract Factory creates multiple related products that work together as a cohesive unit.
Core Concept
The Abstract Factory pattern addresses the challenge of creating entire product families while ensuring that products from the same family are compatible with each other. It encapsulates a group of individual factories that have a common theme, providing a way to create objects that belong to different categories but are designed to work together.
When to Use Abstract Factory
The Abstract Factory pattern is ideal when:
- Your system needs to be independent of how its products are created
- You need to work with families of related products
- You want to enforce constraints that products from the same family are used together
- You need to provide a library of products and reveal only their interfaces
- The system should be configured with one of multiple families of products
Structure and Components
Abstract Factory
┌─────────────────────────┐
│ + create_product_a() │
│ + create_product_b() │
└─────────────────────────┘
▲ ▲
│ │
Factory1 Factory2
┌─────────────┐ ┌─────────────┐
│ + create_ │ │ + create_ │
│ product_a │ │ product_a │
│ + create_ │ │ + create_ │
│ product_b │ │ product_b │
└─────────────┘ └─────────────┘
│ │
┌────┴───┐ ┌───┴────┐
│ │ │ │
Product_A1 Product_A2 Product_B1 Product_B2
The pattern consists of several key components:
- Abstract Factory: Declares creation methods for each type of product
- Concrete Factories: Implement creation methods for specific product families
- Abstract Products: Define interfaces for different types of products
- Concrete Products: Implement abstract product interfaces for specific variants
- Client: Uses only abstract factory and product interfaces
Benefits
- Product Family Consistency: Ensures products from the same family work together
- Separation of Concerns: Isolates concrete classes from client code
- Easy Product Family Switching: Change entire product families by switching factories
- Single Responsibility: Each factory is responsible for one product family
- Open/Closed Principle: Easy to add new product families without modifying existing code
Drawbacks
- Complexity: Introduces many interfaces and classes
- Rigid Structure: Adding new product types requires changes to all factory interfaces
- Abstraction Overhead: May be overkill for simple scenarios
- Maintenance: More classes to maintain and understand
Abstract Factory vs Factory Method
+------------------+---------------------+---------------------------+
| Aspect | Factory Method | Abstract Factory |
+------------------+---------------------+---------------------------+
| Purpose | Create one product | Create families of |
| | type | products |
+------------------+---------------------+---------------------------+
| Complexity | Simpler structure | More complex with multiple|
| | | products |
+------------------+---------------------+---------------------------+
| Inheritance | Uses inheritance | Uses composition |
+------------------+---------------------+---------------------------+
| Products | Single product focus| Multiple related products |
+------------------+---------------------+---------------------------+
| Flexibility | Easy to extend | Easy to add product |
| | single products | families |
+------------------+---------------------+---------------------------+
from abc import ABC, abstractmethod
from typing import Protocol, Dict, Any, List
from enum import Enum
# Abstract Product Interfaces - Define what each product type must implement
class Button(Protocol):
"""Abstract interface for button components."""
def render(self) -> str:
"""Render the button and return its representation."""
...
def handle_click(self) -> str:
"""Handle button click event."""
...
class TextField(Protocol):
"""Abstract interface for text field components."""
def render(self) -> str:
"""Render the text field and return its representation."""
...
def get_value(self) -> str:
"""Get the current value of the text field."""
...
def set_value(self, value: str) -> None:
"""Set the value of the text field."""
...
class Dialog(Protocol):
"""Abstract interface for dialog components."""
def render(self) -> str:
"""Render the dialog and return its representation."""
...
def show(self) -> str:
"""Show the dialog."""
...
def close(self) -> str:
"""Close the dialog."""
...
# Concrete Products for Windows Theme
class WindowsButton:
"""Windows-style button implementation."""
def __init__(self) -> None:
self._text: str = "Windows Button"
def render(self) -> str:
"""Render Windows-style button."""
return f"[{self._text}] (Windows Style - Rectangular with border)"
def handle_click(self) -> str:
"""Handle Windows button click."""
return "Windows button clicked with system sound"
class WindowsTextField:
"""Windows-style text field implementation."""
def __init__(self) -> None:
self._value: str = ""
self._placeholder: str = "Enter text..."
def render(self) -> str:
"""Render Windows-style text field."""
display_text = self._value if self._value else self._placeholder
return f"|{display_text:<20}| (Windows Style - White background)"
def get_value(self) -> str:
"""Get current text field value."""
return self._value
def set_value(self, value: str) -> None:
"""Set text field value."""
self._value = value
class WindowsDialog:
"""Windows-style dialog implementation."""
def __init__(self) -> None:
self._title: str = "Windows Dialog"
self._content: str = "This is a Windows-style dialog"
self._is_open: bool = False
def render(self) -> str:
"""Render Windows-style dialog."""
if not self._is_open:
return "Dialog is closed"
border = "+" + "-" * 40 + "+"
title_line = f"|{self._title:^40}|"
content_line = f"|{self._content:<40}|"
return f"{border}\\n{title_line}\\n{content_line}\\n{border}"
def show(self) -> str:
"""Show Windows dialog."""
self._is_open = True
return "Windows dialog opened with taskbar notification"
def close(self) -> str:
"""Close Windows dialog."""
self._is_open = False
return "Windows dialog closed with fade animation"
# Concrete Products for macOS Theme
class MacOSButton:
"""macOS-style button implementation."""
def __init__(self) -> None:
self._text: str = "macOS Button"
def render(self) -> str:
"""Render macOS-style button."""
return f"({self._text}) (macOS Style - Rounded corners, blue accent)"
def handle_click(self) -> str:
"""Handle macOS button click."""
return "macOS button clicked with subtle haptic feedback"
class MacOSTextField:
"""macOS-style text field implementation."""
def __init__(self) -> None:
self._value: str = ""
self._placeholder: str = "Enter text..."
def render(self) -> str:
"""Render macOS-style text field."""
display_text = self._value if self._value else self._placeholder
return f"⌜{display_text:<20}⌝ (macOS Style - Rounded, shadow)"
def get_value(self) -> str:
"""Get current text field value."""
return self._value
def set_value(self, value: str) -> None:
"""Set text field value."""
self._value = value
class MacOSDialog:
"""macOS-style dialog implementation."""
def __init__(self) -> None:
self._title: str = "macOS Dialog"
self._content: str = "This is a macOS-style dialog"
self._is_open: bool = False
def render(self) -> str:
"""Render macOS-style dialog."""
if not self._is_open:
return "Dialog is closed"
border = "╭" + "─" * 40 + "╮"
bottom = "╰" + "─" * 40 + "╯"
title_line = f"│{self._title:^40}│"
content_line = f"│{self._content:<40}│"
return f"{border}\\n{title_line}\\n{content_line}\\n{bottom}"
def show(self) -> str:
"""Show macOS dialog."""
self._is_open = True
return "macOS dialog opened with smooth slide animation"
def close(self) -> str:
"""Close macOS dialog."""
self._is_open = False
return "macOS dialog closed with spring animation"
# Concrete Products for Web Theme
class WebButton:
"""Web-style button implementation."""
def __init__(self) -> None:
self._text: str = "Web Button"
def render(self) -> str:
"""Render web-style button."""
return f"<button>{self._text}</button> (Web Style - CSS styled)"
def handle_click(self) -> str:
"""Handle web button click."""
return "Web button clicked - JavaScript event fired"
class WebTextField:
"""Web-style text field implementation."""
def __init__(self) -> None:
self._value: str = ""
self._placeholder: str = "Enter text..."
def render(self) -> str:
"""Render web-style text field."""
value_attr = f'value="{self._value}"' if self._value else ""
placeholder_attr = f'placeholder="{self._placeholder}"'
return f"<input type='text' {value_attr} {placeholder_attr}> (Web Style - HTML input)"
def get_value(self) -> str:
"""Get current text field value."""
return self._value
def set_value(self, value: str) -> None:
"""Set text field value."""
self._value = value
class WebDialog:
"""Web-style dialog implementation."""
def __init__(self) -> None:
self._title: str = "Web Dialog"
self._content: str = "This is a web-style dialog"
self._is_open: bool = False
def render(self) -> str:
"""Render web-style dialog."""
if not self._is_open:
return "Dialog is closed"
return f"""<div class="modal">
<div class="modal-header">{self._title}</div>
<div class="modal-body">{self._content}</div>
</div> (Web Style - Modal overlay)"""
def show(self) -> str:
"""Show web dialog."""
self._is_open = True
return "Web dialog opened with CSS transition"
def close(self) -> str:
"""Close web dialog."""
self._is_open = False
return "Web dialog closed with fade-out transition"
# Theme enumeration for better type safety
class UITheme(Enum):
WINDOWS = "windows"
MACOS = "macos"
WEB = "web"
# Abstract Factory Interface
class UIComponentFactory(ABC):
"""Abstract factory for creating UI component families."""
@abstractmethod
def create_button(self) -> Button:
"""Create a button component."""
pass
@abstractmethod
def create_text_field(self) -> TextField:
"""Create a text field component."""
pass
@abstractmethod
def create_dialog(self) -> Dialog:
"""Create a dialog component."""
pass
def get_theme_name(self) -> str:
"""Get the name of the theme this factory creates."""
return self.__class__.__name__.replace("Factory", "")
# Concrete Factories - Each creates a family of related products
class WindowsUIFactory(UIComponentFactory):
"""Factory for creating Windows-themed UI components."""
def create_button(self) -> Button:
"""Create a Windows-style button."""
return WindowsButton()
def create_text_field(self) -> TextField:
"""Create a Windows-style text field."""
return WindowsTextField()
def create_dialog(self) -> Dialog:
"""Create a Windows-style dialog."""
return WindowsDialog()
class MacOSUIFactory(UIComponentFactory):
"""Factory for creating macOS-themed UI components."""
def create_button(self) -> Button:
"""Create a macOS-style button."""
return MacOSButton()
def create_text_field(self) -> TextField:
"""Create a macOS-style text field."""
return MacOSTextField()
def create_dialog(self) -> Dialog:
"""Create a macOS-style dialog."""
return MacOSDialog()
class WebUIFactory(UIComponentFactory):
"""Factory for creating web-themed UI components."""
def create_button(self) -> Button:
"""Create a web-style button."""
return WebButton()
def create_text_field(self) -> TextField:
"""Create a web-style text field."""
return WebTextField()
def create_dialog(self) -> Dialog:
"""Create a web-style dialog."""
return WebDialog()
# Factory Registry and Client Code
class UIFactoryRegistry:
"""Registry to manage UI factory instances."""
def __init__(self) -> None:
self._factories: Dict[UITheme, UIComponentFactory] = {
UITheme.WINDOWS: WindowsUIFactory(),
UITheme.MACOS: MacOSUIFactory(),
UITheme.WEB: WebUIFactory()
}
def get_factory(self, theme: UITheme) -> UIComponentFactory:
"""Get the factory for the specified theme."""
if theme not in self._factories:
raise ValueError(f"No factory available for theme: {theme}")
return self._factories[theme]
def register_factory(self, theme: UITheme, factory: UIComponentFactory) -> None:
"""Register a new factory for a theme."""
self._factories[theme] = factory
# Application class that uses the Abstract Factory
class UIApplication:
"""Application that uses abstract factory to create UI components."""
def __init__(self, factory: UIComponentFactory) -> None:
self._factory = factory
self._components: Dict[str, Any] = {}
def create_login_form(self) -> Dict[str, Any]:
"""Create a complete login form using the factory."""
# Create all components from the same family
button = self._factory.create_button()
username_field = self._factory.create_text_field()
password_field = self._factory.create_text_field()
dialog = self._factory.create_dialog()
# Configure components
username_field.set_value("") # Empty initially
password_field.set_value("") # Empty initially
# Store components
self._components = {
"login_button": button,
"username_field": username_field,
"password_field": password_field,
"error_dialog": dialog
}
return {
"theme": self._factory.get_theme_name(),
"components_created": len(self._components),
"component_types": list(self._components.keys())
}
def render_form(self) -> str:
"""Render the complete login form."""
if not self._components:
return "No form created yet"
form_parts = [
f"=== {self._factory.get_theme_name()} Login Form ===",
f"Username: {self._components['username_field'].render()}",
f"Password: {self._components['password_field'].render()}",
f"Submit: {self._components['login_button'].render()}",
f"Error Dialog: {self._components['error_dialog'].render()}"
]
return "\\n".join(form_parts)
def simulate_interaction(self) -> List[str]:
"""Simulate user interaction with the form."""
if not self._components:
return ["No form available for interaction"]
interactions = []
# Simulate filling out the form
self._components['username_field'].set_value("user@example.com")
interactions.append("User entered username")
self._components['password_field'].set_value("••••••••")
interactions.append("User entered password")
# Simulate button click
click_result = self._components['login_button'].handle_click()
interactions.append(f"Login button clicked: {click_result}")
# Simulate showing error dialog
dialog_show = self._components['error_dialog'].show()
interactions.append(f"Error dialog shown: {dialog_show}")
return interactions
# Demonstration function
def demonstrate_abstract_factory():
"""Demonstrate the Abstract Factory pattern usage."""
print("=== Abstract Factory Pattern Demonstration ===\\n")
registry = UIFactoryRegistry()
# Demonstrate creating UI components for different themes
for theme in UITheme:
print(f"Creating {theme.value.upper()} themed application:")
print("-" * 50)
# Get the appropriate factory
factory = registry.get_factory(theme)
# Create application with the factory
app = UIApplication(factory)
# Create and render login form
creation_info = app.create_login_form()
print(f"Form created: {creation_info}")
print()
# Render the form
print("Rendered Form:")
print(app.render_form())
print()
# Simulate interactions
print("User Interactions:")
interactions = app.simulate_interaction()
for interaction in interactions:
print(f" • {interaction}")
print("\\n" + "="*60 + "\\n")
if __name__ == "__main__":
demonstrate_abstract_factory()
This example demonstrates the Abstract Factory pattern through a cross-platform UI component system. Here’s how each component works together:
- Abstract Product Interfaces: Button, TextField, and Dialog protocols define the contracts that all UI components must follow, regardless of their specific theme implementation.
- Concrete Product Families: Three complete families of UI components are implemented:
- Windows Family: Components with rectangular borders and system-native behavior
- macOS Family: Components with rounded corners and smooth animations
- Web Family: Components that render as HTML elements with CSS styling
- Abstract Factory (UIComponentFactory): Defines the interface for creating entire families of related UI components. Each factory method creates one type of component, but all components from the same factory belong to the same visual theme.
- Concrete Factories: WindowsUIFactory, MacOSUIFactory, and WebUIFactory each implement the abstract factory interface to create their respective component families. This ensures that all components created by a single factory instance will have a consistent look and feel.
- Client Application (UIApplication): Demonstrates how to use the abstract factory without knowing the concrete classes. The application creates a complete login form using components from a single family, ensuring visual consistency.
- Factory Registry: Provides a centralized way to manage and retrieve factories, making it easy to switch between different UI themes at runtime.
Key Implementation Details
- Protocol-Based Design: Uses Python protocols instead of abstract base classes for more flexible typing and better separation of concerns
- Family Consistency: All components created by the same factory share visual and behavioral characteristics
- Runtime Theme Switching: The registry allows dynamic selection of UI themes
- Type Safety: Enums and type hints provide compile-time checking and better IDE support
- Realistic Simulation: The example includes an interaction simulation to show how components from the same family work together