Abstract Factory Design Pattern in C# with Example

1. Definition

The Abstract Factory Pattern is a creational design pattern that produces families of related or dependent objects without specifying their concrete classes.

2. Problem Statement

Consider a scenario where you are building a UI library that needs to be consistent across multiple platforms, like Windows, MacOS, and Linux. Each platform has its set of UI elements: buttons, checkboxes, windows, etc. How do you ensure that each UI element is instantiated properly according to its platform while keeping the creation process uniform?

3. Solution

The Abstract Factory pattern provides an interface for creating families of related objects without specifying their concrete classes. This way, the client code that uses the factory is decoupled from the concrete classes responsible for implementing the objects.

4. Real-World Use Cases

1. Creating GUI libraries that should be consistent but adaptable to multiple platforms.

2. Designing product sets with multiple variations, but where each product in a set needs to be compatible with the products of the same set.

3. Providing a system for creating entire families of products with certain shared characteristics.

5. Implementation Steps

1. Declare abstract product interfaces for a set of distinct but related products.

2. Create concrete classes for each product family.

3. Declare the abstract factory interface with creation methods for each abstract product.

4. Implement concrete factories for each product family.

5. Use the concrete factory to instantiate products, ensuring they belong to the same family.

6. Implementation in C#

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

public interface ICheckbox
{
    void Check();
}

// Concrete products for Windows
public class WindowsButton : IButton
{
    public void Render()
    {
        Console.WriteLine("Rendering a Windows button.");
    }
}

public class WindowsCheckbox : ICheckbox
{
    public void Check()
    {
        Console.WriteLine("Checking a Windows checkbox.");
    }
}

// Concrete products for MacOS
public class MacOSButton : IButton
{
    public void Render()
    {
        Console.WriteLine("Rendering a MacOS button.");
    }
}

public class MacOSCheckbox : ICheckbox
{
    public void Check()
    {
        Console.WriteLine("Checking a MacOS checkbox.");
    }
}

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

// Concrete Factories
public class WindowsUIFactory : IUIFactory
{
    public IButton CreateButton() => new WindowsButton();

    public ICheckbox CreateCheckbox() => new WindowsCheckbox();
}

public class MacOSUIFactory : IUIFactory
{
    public IButton CreateButton() => new MacOSButton();

    public ICheckbox CreateCheckbox() => new MacOSCheckbox();
}

public class Program
{
    public static void Main()
    {
        IUIFactory factory;

        if (Environment.OSVersion.Platform == PlatformID.Win32NT)
        {
            factory = new WindowsUIFactory();
        }
        else
        {
            factory = new MacOSUIFactory();
        }

        var button = factory.CreateButton();
        button.Render();

        var checkbox = factory.CreateCheckbox();
        checkbox.Check();
    }
}

Output:

Rendering a Windows button.
Checking a Windows checkbox.
// Output will depend on the OS. Above output is for Windows.

Explanation:

In the provided example, IButton and ICheckbox are the abstract product interfaces. 

For each platform (Windows and MacOS), there are concrete implementations of these interfaces. 

The IUIFactory interface is the Abstract Factory, which declares methods to create each kind of product. 

The WindowsUIFactory and MacOSUIFactory are concrete factories that implement the IUIFactory interface. 

The Program class demonstrates how the client can use the factory to create UI elements in a platform-independent manner.

7. When to use?

Use the Abstract Factory pattern when:

1. The system should be independent of how its objects are created, composed, and represented.

2. The system must be configured with one of multiple families of products.

3. You want to provide a library of products, and you want to reveal just their interfaces, not their implementations.


Comments