Abstract Factory Pattern in C#: A Practical Guide

The Abstract Factory pattern is a creational design pattern that allows you to produce families of related or dependent objects without specifying their concrete classes. This article explores the core definition of the pattern, breaks down its structural components, and demonstrates how to implement it cleanly in C# through a cross-platform user interface example.

What is the Abstract Factory Pattern?

In software engineering, systems often need to create groups of related objects that must work seamlessly together. The Abstract Factory pattern addresses this by declaring an interface for creating each distinct product in a product family. Concrete classes then implement these creation methods to return specific product variants.

By isolating the client code from concrete implementations, the pattern enforces consistency among products, adheres to the Single Responsibility Principle, and satisfies the Open/Closed Principle by allowing new product variants without breaking existing code.

Core Structure of the Pattern

The pattern consists of five primary components:

  1. Abstract Factory: Declares an interface containing methods for creating each abstract product.
  2. Concrete Factories: Implements the creation operations to produce specific instances of concrete products.
  3. Abstract Products: Declares interfaces for a set of distinct but related products that make up a product family.
  4. Concrete Products: Provide the implementations for abstract product interfaces, grouped by variant.
  5. Client: Interacts only with objects declared by the Abstract Factory and Abstract Product interfaces.

Implementing Abstract Factory in C

The following example models a cross-platform UI toolkit that creates buttons and checkboxes for two platforms: Windows and macOS.

1. Abstract Products

First, define the interfaces for the individual components that belong to the family.

public interface IButton
{
    void Render();
}

public interface ICheckbox
{
    void Toggle();
}

2. Concrete Products

Implement these interfaces for each specific platform variant.

// Windows variants
public class WinButton : IButton
{
    public void Render() => Console.WriteLine("Rendering Windows-style Button.");
}

public class WinCheckbox : ICheckbox
{
    public void Toggle() => Console.WriteLine("Toggling Windows-style Checkbox.");
}

// Mac variants
public class MacButton : IButton
{
    public void Render() => Console.WriteLine("Rendering macOS-style Button.");
}

public class MacCheckbox : ICheckbox
{
    public void Toggle() => Console.WriteLine("Toggling macOS-style Checkbox.");
}

3. Abstract Factory

Define the factory interface containing creation methods for all abstract products in the family.

public interface IUIFactory
{
    IButton CreateButton();
    ICheckbox CreateCheckbox();
}

4. Concrete Factories

Implement the factory interface to return specific product variants that correspond to a single platform.

public class WinUIFactory : IUIFactory
{
    public IButton CreateButton() => new WinButton();
    public ICheckbox CreateCheckbox() => new WinCheckbox();
}

public class MacUIFactory : IUIFactory
{
    public IButton CreateButton() => new MacButton();
    public ICheckbox CreateCheckbox() => new MacCheckbox();
}

5. Client Code

The client interacts solely with the abstract interfaces, remaining decoupled from the underlying operating system details.

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

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

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

6. Execution

The application is configured with a specific factory at runtime.

class Program
{
    static void Main()
    {
        // The factory can be chosen based on environment or configuration
        IUIFactory factory = OperatingSystem.IsWindows() 
            ? new WinUIFactory() 
            : new MacUIFactory();

        Application app = new Application(factory);
        app.RenderUI();
    }
}

When to Use

Use the Abstract Factory pattern when your code needs to work with various families of related products, but you want to prevent tight coupling to concrete product classes. It is particularly effective when you must ensure that products from one family are never mixed with products from another.