🎨 Abstract Factory Pattern in C# 14

Create families of related objects without specifying their concrete classes

🧭 Introduction

In modern C# development, particularly within enterprise applications, it’s common to deal with families of related objects. These objects are meant to work together cohesively, but the concrete implementations may vary depending on the context, for example, environment, theme, platform, or user settings. Manually switching out these implementations can lead to tight coupling and unmanageable code as complexity grows. This is exactly where the Abstract Factory pattern becomes essential.

The Abstract Factory pattern is a creational design pattern that allows you to create families of related objects without knowing their exact classes. It provides a way to encapsulate a group of individual factories with a common interface. In this article, we’ll explore how to implement this pattern using C# 14 and demonstrate it with a real-world scenario: a UI Theming System that supports both Light and Dark themes. You'll see how the Abstract Factory makes it easy to switch entire object families cleanly and consistently.

🧠 Why Use Abstract Factory?

The Abstract Factory pattern is particularly useful when your application must support multiple "variants" or "families" of objects that should work seamlessly together. In UI development, for instance, all components like buttons, textboxes, sliders, etc., should visually and behaviorally align with the selected theme. Manually wiring these up for every variation not only becomes repetitive but also error-prone. Abstract Factory solves this by centralizing the creation logic of these related components.

Another major advantage is maintainability and extensibility. Imagine adding a new theme like "HighContrast" or "GreenDark". With this pattern in place, you'd only need to implement a new factory and its matching components, leaving the rest of the system untouched. The UI rendering logic doesn't care which theme is active; it just calls the factory.CreateButton() or factory.CreateTextBox(). This allows you to obey the Open/Closed Principle—open for extension but closed for modification.

🖼️ Real-World Example: UI Theming

User interfaces are a textbook example of where the Abstract Factory pattern shines. Different themes often need different components: a dark-themed Button will look different from a light-themed Button, but they should behave the same and offer the same interface to the rest of the system. Without a clear abstraction, swapping between themes would require manually changing each individual component class, leading to tight coupling and repetition.

In this example, we’ll design a simplified UI framework with just two components: Button and TextBox. These will have two concrete variants each—Light and Dark. The UI rendering logic will be decoupled from specific implementations and rely only on abstract interfaces. This makes it easy to switch themes at runtime or configuration time by simply providing a different factory implementation.

✅ Step-by-Step Implementation in C# 14

The implementation begins with defining abstract interfaces for the components: IButton and ITextBox. These interfaces ensure that all concrete variants share a common structure and contract. For instance, both LightButton and DarkButton will implement IButton, and both LightTextBox and DarkTextBox will implement ITextBox. This standardization is key to enabling interchangeable object families.

Next, we create the abstract factory interface, IUIFactory, which declares methods for creating each component. The client code will depend on this interface rather than concrete implementations. This separation of concerns allows us to provide different factories—like LightThemeFactory or DarkThemeFactory—that internally decide which exact object variants to instantiate, keeping client code simple and flexible.

🔧 Interfaces for Components

public interface IButton
{
    void Render();
}

public interface ITextBox
{
    void Render();
}

These interfaces are the backbone of the pattern. By defining consistent contracts, you make it possible for any number of implementations to be plugged in and swapped out, without the rest of your code caring. The Render() method is a placeholder for more complex behavior like drawing the component to a screen or applying animations.

🏗️ Abstract Factory Interface

public interface IUIFactory
{
    IButton CreateButton();
    ITextBox CreateTextBox();
}

This interface is the Abstract Factory itself. It defines a method for each component you want to produce. By relying on this abstraction, your client code (e.g., UI screens or renderers) can request objects from a factory without needing to know anything about the actual theme or implementation being used behind the scenes.

🌞 Light Theme Components

Light-themed components visually align with UI systems designed for daylight or bright conditions. These implementations follow the contracts set by IButton and ITextBox, but their behaviors or styles reflect the light theme. This can include lighter background colors, darker text, or shadow effects.

Here’s how they’re defined in code:

public class LightButton : IButton
{
    public void Render() => Console.WriteLine("Rendering Light Button");
}

public class LightTextBox : ITextBox
{
    public void Render() => Console.WriteLine("Rendering Light TextBox");
}

These classes might look simple here, but in real-world applications, they’d often include styling logic, references to CSS-like definitions, or even inject theme services. Still, the concept is the same—they fulfill an interface without exposing theme-specific logic to the outside world. 

🌚 Dark Theme Components

Dark-themed components are built for environments where low-light readability or battery savings are desired. These components might use high-contrast text, dark backgrounds, and glow effects. They are plug-and-play substitutes for their light-themed counterparts because they adhere to the same interface contracts.

Here's the code:

public class DarkButton : IButton
{
    public void Render() => Console.WriteLine("Rendering Dark Button");
}

public class DarkTextBox : ITextBox
{
    public void Render() => Console.WriteLine("Rendering Dark TextBox");
}

Swapping these components into the UI is effortless, as the client code doesn't need to change. The only switch required is in the factory instance provided—another benefit of using the Abstract Factory.

🧱 Factory Implementations

Each factory implementation corresponds to a theme. These concrete factories create the appropriate themed components. Whether the app loads a dark or light factory depends on the user's settings, system preferences, or configuration.

public class LightThemeFactory : IUIFactory
{
    public IButton CreateButton() => new LightButton();
    public ITextBox CreateTextBox() => new LightTextBox();
}

public class DarkThemeFactory : IUIFactory
{
    public IButton CreateButton() => new DarkButton();
    public ITextBox CreateTextBox() => new DarkTextBox();
}

This setup lets your application easily extend into additional themes. If you later want to support a "High Contrast" theme for accessibility, you only need to create new component classes and a HighContrastThemeFactory—no need to modify your rendering logic.

🧪 Client Code Example

The client class, UIComponentRenderer, depends only on the abstract factory. This class doesn’t care which theme is used; it simply asks the factory for the necessary components and renders them. This is the key to decoupling.

public class UIComponentRenderer
{
    private readonly IButton _button;
    private readonly ITextBox _textBox;

    public UIComponentRenderer(IUIFactory factory)
    {
        _button = factory.CreateButton();
        _textBox = factory.CreateTextBox();
    }

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

Now, rendering a UI becomes straightforward, no matter how many themes exist. The renderer can be reused in mobile, desktop, or web contexts with minimal changes.

▶️ Main Entry Point

class Program
{
    static void Main()
    {
        // Switch between themes by changing the factory
        IUIFactory factory = new LightThemeFactory();
        // IUIFactory factory = new DarkThemeFactory();

        var renderer = new UIComponentRenderer(factory);
        renderer.Render();
    }
}

Switching between themes is as simple as choosing a different factory. This approach is scalable, readable, and easy to maintain.

📈 Benefits of Enterprise Software

The Abstract Factory pattern becomes incredibly useful in systems that need plug-and-play configurability. In enterprise settings, you might use it to switch between:

  • Database providers (e.g., SQL Server vs. PostgreSQL).
  • Environment-specific implementations (e.g., dev, staging, production).
  • Cross-platform UI components (e.g., iOS vs. Android).

This pattern also plays nicely with Dependency Injection (DI) frameworks. You can register factories per environment in your container, allowing complete control over how object families are constructed at runtime.

✅ Summary

The Abstract Factory Pattern is a powerful solution when your application deals with interdependent families of objects. Instead of hardcoding specific implementations or wiring up classes manually, this pattern gives you a clean and scalable way to define contracts, centralize instantiation, and easily swap variants.

Using C# 14, you get the benefit of more expressive code and better architectural clarity. With minimal effort, your application can support multiple themes, platforms, or environments—without bloating your logic or creating brittle dependencies.

Up Next
    Ebook Download
    View all
    Learn
    View all