Published on

[Creational] Abstract Factory

Abstract Factory Design Pattern

The Abstract Factory pattern is a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. It helps enforce consistency among products and makes it easier to introduce new variants without modifying existing code.


Structure

  1. Abstract Factory (AbstractFactory)

    • Declares a set of methods to create abstract products.
  2. Concrete Factories (ConcreteFactoryA, ConcreteFactoryB)

    • Implements the abstract factory to create specific product variants.
  3. Abstract Products (AbstractProductX, AbstractProductY)

    • Defines interfaces for product families.
  4. Concrete Products (ProductAX, ProductAY, ProductBX, ProductBY)

    • Implements product interfaces with specific variations.
  5. Client (Client)

    • Uses the factory to create product objects without needing to know their concrete classes.

Example: GUI Factory

Let's implement an abstract factory that creates UI elements (Button and Checkbox) for different operating systems (Windows and Mac).

Step 1: Define Abstract Products

from abc import ABC, abstractmethod

# Abstract Product A
class Button(ABC):
    @abstractmethod
    def render(self) -> str:
        pass

# Abstract Product B
class Checkbox(ABC):
    @abstractmethod
    def render(self) -> str:
        pass

Step 2: Define Concrete Products

# Concrete Product A1
class WindowsButton(Button):
    def render(self) -> str:
        return "Rendering a Windows-style button"

# Concrete Product A2
class MacButton(Button):
    def render(self) -> str:
        return "Rendering a Mac-style button"

# Concrete Product B1
class WindowsCheckbox(Checkbox):
    def render(self) -> str:
        return "Rendering a Windows-style checkbox"

# Concrete Product B2
class MacCheckbox(Checkbox):
    def render(self) -> str:
        return "Rendering a Mac-style checkbox"

Step 3: Define Abstract Factory

# Abstract Factory
class UIFactory(ABC):
    @abstractmethod
    def create_button(self) -> Button:
        pass

    @abstractmethod
    def create_checkbox(self) -> Checkbox:
        pass

Step 4: Implement Concrete Factories

# Concrete Factory 1 - Windows Factory
class WindowsFactory(UIFactory):
    def create_button(self) -> Button:
        return WindowsButton()

    def create_checkbox(self) -> Checkbox:
        return WindowsCheckbox()

# Concrete Factory 2 - Mac Factory
class MacFactory(UIFactory):
    def create_button(self) -> Button:
        return MacButton()

    def create_checkbox(self) -> Checkbox:
        return MacCheckbox()

Step 5: Create Client Code

# Client Code
def client_code(factory: UIFactory):
    button = factory.create_button()
    checkbox = factory.create_checkbox()
    
    print(button.render())
    print(checkbox.render())

# Testing
windows_factory = WindowsFactory()
mac_factory = MacFactory()

print("Windows UI:")
client_code(windows_factory)

print("\nMac UI:")
client_code(mac_factory)

Output

Windows UI:
Rendering a Windows-style button
Rendering a Windows-style checkbox

Mac UI:
Rendering a Mac-style button
Rendering a Mac-style checkbox

Advantages of Abstract Factory Pattern

Encapsulation of Object Creation – The client does not need to know the concrete classes. ✅ Consistency among Products – Ensures that products in a family match. ✅ Easy to Introduce New Variants – New factories can be added without modifying existing client code. ✅ Promotes Separation of Concerns – Factories manage object creation, while products handle their own behavior.

When to Use?

  • When you need to create families of related objects.
  • When enforcing consistency among related objects is essential.
  • When the system should be independent of how its objects are created.