patron-abstract-factory

Abstract Factory Pattern: Families of Objects

  • 5 min

The Abstract Factory is an interface for creating families of related objects without specifying their concrete classes. The client receives a factory and works with the abstract products it offers.

In the previous article, we saw how Factory Method saved us from using new to create a single type of product (like a Transport).

But what happens when our objects don’t come alone? What happens when we need to create families of objects that must work together and be coherent with each other?

Imagine you’re developing a graphical interface. If the user chooses the dark theme, all elements must maintain that style. You don’t want to mix a Windows 95 button with a modern macOS window.

The pattern allows keeping products created together coherent.

The Problem: Families of Products

Let’s say we’re building a medieval and futuristic battle simulator.

We have two product families:

  1. Medieval Family: Orc, Castle, Sword.
  2. Futuristic Family: Alien, SpaceBase, Laser.

If we use a simple Factory Method, we could make a mistake and end up creating an Orc armed with a Laser inside a SpaceBase. The compiler wouldn’t complain, but the game logic would be broken.

We need a way to tell the system: “Give me all the necessary objects for the Medieval scenario”, and have the system guarantee that all returned objects belong to that era.

The Solution: A Factory of Factories

The Abstract Factory pattern defines an interface for creating families of related or dependent objects without specifying their concrete classes.

Basically, we create an interface (the “Abstract Factory”) that has a list of creation methods for each product type in the family (CreateWarrior, CreateWeapon, CreateBuilding).

Then, we create concrete factories (MedievalFactory, FuturisticFactory) that implement that interface and return the corresponding products.

Implementation in C# (UI Example)

Let’s implement the classic example of a UI library that supports two operating systems: Windows and Mac. We want buttons and checkboxes that match the OS.

The Abstract and Concrete Products

First, we define the interfaces for our components and their implementations.

// --- PRODUCT A: BUTTON ---
public interface IButton
{
    void Paint();
}

public class WindowsButton : IButton
{
    public void Paint() => Console.WriteLine("Rendering a WINDOWS-style button");
}

public class MacButton : IButton
{
    public void Paint() => Console.WriteLine("Rendering a MAC-style button");
}

// --- PRODUCT B: CHECKBOX ---
public interface ICheckbox
{
    void Paint();
}

public class WindowsCheckbox : ICheckbox
{
    public void Paint() => Console.WriteLine("Rendering a WINDOWS-style checkbox");
}

public class MacCheckbox : ICheckbox
{
    public void Paint() => Console.WriteLine("Rendering a MAC-style checkbox");
}
Copied!

The Abstract Factory

Here we define the contract: any UI factory must know how to create buttons and checkboxes.

public interface IGUIFactory
{
    IButton CreateButton();
    ICheckbox CreateCheckbox();
}
Copied!

The Concrete Factories

Here we ensure coherence. The WinFactory only returns Windows objects. It is impossible to mix styles here.

public class WinFactory : IGUIFactory
{
    public IButton CreateButton()
    {
        return new WindowsButton();
    }

    public ICheckbox CreateCheckbox()
    {
        return new WindowsCheckbox();
    }
}

public class MacFactory : IGUIFactory
{
    public IButton CreateButton()
    {
        return new MacButton();
    }

    public ICheckbox CreateCheckbox()
    {
        return new MacCheckbox();
    }
}
Copied!

The Client (The Application)

The client doesn’t know if it’s on Windows or Mac. It only knows it has a factory and asks it for things.

public class Application
{
    private readonly IButton _button;
    private readonly ICheckbox _checkbox;

    // Dependency injection: We receive the abstract factory
    public Application(IGUIFactory factory)
    {
        // Create the product family
        _button = factory.CreateButton();
        _checkbox = factory.CreateCheckbox();
    }

    public void Paint()
    {
        _button.Paint();
        _checkbox.Paint();
    }
}
Copied!

Execution

class Program
{
    static void Main(string[] args)
    {
        // Simulate environment configuration
        IGUIFactory factory;
        string os = "Windows"; // This would come from config

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

        // The application works the same regardless of the factory
        var app = new Application(factory);
        app.Paint();
    }
}
Copied!

Output:

Rendering a WINDOWS-style button
Rendering a WINDOWS-style checkbox
Copied!

If we change os = "Mac", the whole application changes its appearance automatically, and we have the guarantee that there won’t be a stray Windows checkbox.

Advantages and Disadvantages

AdvantagesDisadvantages
Consistency: Guarantees that products used together are compatible.Complexity: Code becomes filled with interfaces and classes. If you only have two classes, it’s overkill.
Decoupling: The client doesn’t touch concrete classes.Rigidity: Adding a new product type (e.g., CreateSlider) is painful. You have to change the IGUIFactory interface and all concrete factories.

Difference from Factory Method

It’s easy to get confused, so here’s a practical idea:

  • Factory Method: Used to create a single product. It relies on inheritance (delegates to subclasses).
  • Abstract Factory: Used to create families of products. It relies on composition (the client holds a reference to the factory).

In fact, an Abstract Factory is often implemented using Factory Methods within it.