0
0
LldConceptBeginner · 3 min read

Abstract Factory Pattern: Definition, Example, and Use Cases

The Abstract Factory Pattern is a design pattern that provides an interface to create families of related or dependent objects without specifying their concrete classes. It helps to encapsulate object creation, making the system independent of how its objects are created and composed.
⚙️

How It Works

Imagine you want to build different types of furniture sets, like Victorian or Modern. Each set includes a chair, a sofa, and a coffee table. The Abstract Factory Pattern acts like a factory that can create all these pieces for a specific style without you needing to know the exact details of each piece.

In software, this pattern defines an interface for creating related objects, and concrete factories implement this interface to produce objects of a particular family. This way, your code can work with any family of objects through the abstract interface, making it easy to switch families without changing the code that uses them.

💻

Example

This example shows how an abstract factory creates different types of buttons and checkboxes for two operating systems: Windows and MacOS.
python
from abc import ABC, abstractmethod

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

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

# Concrete products for Windows
class WindowsButton(Button):
    def paint(self):
        return "Rendering a button in Windows style"

class WindowsCheckbox(Checkbox):
    def paint(self):
        return "Rendering a checkbox in Windows style"

# Concrete products for MacOS
class MacOSButton(Button):
    def paint(self):
        return "Rendering a button in MacOS style"

class MacOSCheckbox(Checkbox):
    def paint(self):
        return "Rendering a checkbox in MacOS style"

# Abstract factory interface
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 MacOSFactory(GUIFactory):
    def create_button(self) -> Button:
        return MacOSButton()

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

# Client code

def client_code(factory: GUIFactory):
    button = factory.create_button()
    checkbox = factory.create_checkbox()
    print(button.paint())
    print(checkbox.paint())

print("Client: Testing client code with Windows factory:")
client_code(WindowsFactory())

print("\nClient: Testing client code with MacOS factory:")
client_code(MacOSFactory())
Output
Client: Testing client code with Windows factory: Rendering a button in Windows style Rendering a checkbox in Windows style Client: Testing client code with MacOS factory: Rendering a button in MacOS style Rendering a checkbox in MacOS style
🎯

When to Use

Use the Abstract Factory Pattern when your system needs to create families of related objects without depending on their concrete classes. It is helpful when you want to ensure that products from one family are used together and to make your code flexible to support new families easily.

For example, in a cross-platform UI toolkit, you can use this pattern to create UI elements that match the look and feel of different operating systems. Another use case is in a game engine where you want to create different sets of game assets for various themes or levels.

Key Points

  • Provides an interface for creating families of related objects.
  • Encapsulates object creation to keep client code independent of concrete classes.
  • Supports easy swapping of product families without changing client code.
  • Helps maintain consistency among products of the same family.

Key Takeaways

Abstract Factory creates related objects without specifying their concrete classes.
It helps keep client code independent and flexible to changes in product families.
Use it when you need to ensure compatibility among a set of related objects.
It simplifies adding new product families without modifying existing code.