<aside> ๐Ÿ“š ๐Ÿ“ Category: Creational Pattern

</aside>

Overview

Abstract Factory is a creational design pattern that lets you produce families of related objects without specifying their concrete classes. It provides an interface for creating families of related or dependent objects without specifying their concrete classes.

<aside> โ„น๏ธ When to use: When your code needs to work with various families of related products, but you don't want it to depend on the concrete classes of those products. Use when you want to provide a class library of products, and you want to reveal just their interfaces, not their implementations.

</aside>

Problem

Imagine you're creating a furniture shop simulator. Your code consists of classes that represent: a family of related products, say: Chair + Sofa + CoffeeTable. You need different variants of these products: Modern, Victorian, ArtDeco. You need a way to create individual furniture objects so that they match other objects of the same family.

Solution

The Abstract Factory pattern suggests explicitly declaring interfaces for each distinct product of the product family. Then you can make all variants of products follow those interfaces. The next step is to declare the Abstract Factoryโ€”an interface with a list of creation methods for all products.

Implementation in C#

// Abstract products
public interface IButton
{
    void Render();
    void OnClick();
}

public interface ICheckbox
{
    void Render();
    void Toggle();
}

// Concrete products - Windows family
public class WindowsButton : IButton
{
    public void Render() => Console.WriteLine("Rendering Windows button");
    public void OnClick() => Console.WriteLine("Windows button clicked");
}

public class WindowsCheckbox : ICheckbox
{
    public void Render() => Console.WriteLine("Rendering Windows checkbox");
    public void Toggle() => Console.WriteLine("Windows checkbox toggled");
}

// Concrete products - MacOS family
public class MacButton : IButton
{
    public void Render() => Console.WriteLine("Rendering Mac button");
    public void OnClick() => Console.WriteLine("Mac button clicked");
}

public class MacCheckbox : ICheckbox
{
    public void Render() => Console.WriteLine("Rendering Mac checkbox");
    public void Toggle() => Console.WriteLine("Mac checkbox toggled");
}
// Abstract Factory interface
public interface IGUIFactory
{
    IButton CreateButton();
    ICheckbox CreateCheckbox();
}

// Concrete Factories
public class WindowsFactory : IGUIFactory
{
    public IButton CreateButton() => new WindowsButton();
    public ICheckbox CreateCheckbox() => new WindowsCheckbox();
}

public class MacFactory : IGUIFactory
{
    public IButton CreateButton() => new MacButton();
    public ICheckbox CreateCheckbox() => new MacCheckbox();
}
public class Application
{
    private readonly IButton _button;
    private readonly ICheckbox _checkbox;

    public Application(IGUIFactory factory)
    {
        _button = factory.CreateButton();
        _checkbox = factory.CreateCheckbox();
    }

    public void Render()
    {
        _button.Render();
        _checkbox.Render();
    }
}

// Usage
class Program
{
    static void Main()
    {
        IGUIFactory factory;
        string os = "Windows"; // Could be determined at runtime

        if (os == "Windows")
            factory = new WindowsFactory();
        else
            factory = new MacFactory();

        var app = new Application(factory);
        app.Render();
    }
}

Implementation in Python

from abc import ABC, abstractmethod

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

class Checkbox(ABC):
    @abstractmethod
    def render(self):
        pass
    
    @abstractmethod
    def toggle(self):
        pass

# Concrete Products
class WindowsButton(Button):
    def render(self):
        return "Rendering Windows button"
    
    def on_click(self):
        return "Windows button clicked"

class WindowsCheckbox(Checkbox):
    def render(self):
        return "Rendering Windows checkbox"
    
    def toggle(self):
        return "Windows checkbox toggled"

class MacButton(Button):
    def render(self):
        return "Rendering Mac button"
    
    def on_click(self):
        return "Mac button clicked"

class MacCheckbox(Checkbox):
    def render(self):
        return "Rendering Mac checkbox"
    
    def toggle(self):
        return "Mac checkbox toggled"

# 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
class Application:
    def __init__(self, factory: GUIFactory):
        self.button = factory.create_button()
        self.checkbox = factory.create_checkbox()
    
    def render(self):
        print(self.button.render())
        print(self.checkbox.render())

# Usage
factory = WindowsFactory()
app = Application(factory)
app.render()

Real-World Example: Database Factory

// Products
public interface IConnection
{
    void Connect();
}

public interface ICommand
{
    void Execute(string sql);
}

// SQL Server implementations
public class SqlConnection : IConnection
{
    public void Connect() => Console.WriteLine("Connected to SQL Server");
}

public class SqlCommand : ICommand
{
    public void Execute(string sql) => Console.WriteLine($"SQL: {sql}");
}

// PostgreSQL implementations
public class PostgresConnection : IConnection
{
    public void Connect() => Console.WriteLine("Connected to PostgreSQL");
}

public class PostgresCommand : ICommand
{
    public void Execute(string sql) => Console.WriteLine($"PG: {sql}");
}

// Abstract Factory
public interface IDatabaseFactory
{
    IConnection CreateConnection();
    ICommand CreateCommand();
}

// Concrete Factories
public class SqlServerFactory : IDatabaseFactory
{
    public IConnection CreateConnection() => new SqlConnection();
    public ICommand CreateCommand() => new SqlCommand();
}

public class PostgresFactory : IDatabaseFactory
{
    public IConnection CreateConnection() => new PostgresConnection();
    public ICommand CreateCommand() => new PostgresCommand();
}

// Usage
public class DataAccess
{
    private readonly IConnection _connection;
    private readonly ICommand _command;

    public DataAccess(IDatabaseFactory factory)
    {
        _connection = factory.CreateConnection();
        _command = factory.CreateCommand();
    }

    public void ExecuteQuery(string sql)
    {
        _connection.Connect();
        _command.Execute(sql);
    }
}

Pros and Cons