Skip to main content

Abstract Factory Pattern

Create families of related objects while respecting platform constraints

TL;DR

Abstract Factory provides an interface for creating families of related or dependent objects without specifying their concrete classes. Use it when you need to ensure related objects are created together and swap entire families based on configuration or environment.

Learning Objectives

  • You will be able to identify when products form a family requiring consistent creation.
  • You will be able to distinguish Abstract Factory from Factory Method.
  • You will be able to implement a concrete factory that creates a coherent family of objects.
  • You will be able to swap entire product families without modifying client code.

Motivating Scenario

Your UI framework supports Windows and macOS. Each platform requires specific button, checkbox, and window implementations. Mixing a Windows button with a macOS window breaks the UI contract. Abstract Factory guarantees that all components come from the same family: either all Windows or all macOS. Adding Linux support means implementing one new factory—no changes to client code.

Core Concepts

Abstract Factory defines interfaces for creating related product families. Each concrete factory implements this interface, producing a complete set of objects that work together.

Key elements:

  • AbstractFactory: declares methods for creating each product type
  • ConcreteFactory: implements the factory interface, instantiating specific product families
  • AbstractProduct: interface for a product type
  • ConcreteProduct: specific implementations belonging to a family
Abstract Factory structure

Practical Example

from abc import ABC, abstractmethod

# Abstract Products
class Button(ABC):
@abstractmethod
def paint(self):
pass

class Checkbox(ABC):
@abstractmethod
def paint(self):
pass

# Windows Products
class WindowsButton(Button):
def paint(self):
return "Render a Windows button"

class WindowsCheckbox(Checkbox):
def paint(self):
return "Render a Windows checkbox"

# macOS Products
class MacButton(Button):
def paint(self):
return "Render a macOS button"

class MacCheckbox(Checkbox):
def paint(self):
return "Render a macOS checkbox"

# 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
def render_ui(factory: GUIFactory):
button = factory.create_button()
checkbox = factory.create_checkbox()
print(button.paint())
print(checkbox.paint())

# Usage
windows_factory = WindowsFactory()
render_ui(windows_factory)

mac_factory = MacFactory()
render_ui(mac_factory)

When to Use / When Not to Use

Use Abstract Factory when:
  1. You have families of related objects that must be created together
  2. You want to ensure products from different families don't mix
  3. You need to swap entire product families based on configuration
  4. Client code should depend on abstractions, not concrete implementations
  5. You anticipate adding new families (e.g., new platform support)
Consider alternatives when:
  1. You only have one or two product families (Factory Method may suffice)
  2. Products aren't actually related (simple polymorphism is cleaner)
  3. Configuration is complex and varies per-product (use Dependency Injection)
  4. You need to vary both families and individual products dynamically (use Builder)
  5. The factory itself is trivial or only needed in one place

Patterns and Pitfalls

Patterns and Pitfalls

Pair Abstract Factory with environment detection to instantiate the right family at startup.
If products from different families have hidden dependencies, refactor to make those dependencies explicit or reconsider the pattern.
Adding a new product type requires extending all concrete factories, but client code remains unchanged.

Design Review Checklist

  • Products genuinely form related families that should be created together
  • The factory interface declares methods for creating each product type
  • Each concrete factory creates a complete set of products from one family
  • Client code depends on the AbstractFactory interface, not concrete factories
  • All products implement their respective abstract interfaces
  • Swapping one factory for another doesn't require changing client code
  • Adding a new family only requires implementing one new concrete factory
  • The pattern actually simplifies client code compared to directly using new

Self-Check

  1. Identify families: In your domain, what groups of objects are always used together?
  2. Design factories: Write an abstract factory and at least two concrete implementations.
  3. Verify consistency: Confirm that a factory's products work cohesively without hidden cross-family dependencies.
info

One Takeaway: Abstract Factory ensures related objects are created as coherent families. It shines when you need to support multiple platforms, themes, or configurations and want client code unaware of which implementation is active.

Next Steps

  • Compare with Factory Method to understand when each applies.
  • Study Builder when individual products require complex configuration.
  • Explore Strategy for runtime behavior variation that doesn't require object family changes.

References

  • Gang of Four: Design Patterns (Abstract Factory)
  • Refactoring: Improving the Design of Existing Code (Introduce Factory Method)
  • Freeman & Freeman: Head First Design Patterns