Saturday, June 27, 2026

Abstract Factory Design Pattern

Mastering Design Patterns in C# and ASP.NET Core

Part 2.3 – Abstract Factory Design Pattern

Series: Design Patterns in C# and ASP.NET Core

Pattern Category: Creational Design Pattern

Difficulty: ⭐⭐⭐☆☆ (Intermediate)

Prerequisites: OOP Concepts, Interfaces, Factory Method Pattern, Dependency Injection


Table of Contents

  1. Introduction

  2. What is the Abstract Factory Pattern?

  3. Why Do We Need It?

  4. Problem Statement

  5. Real-World Analogy

  6. Factory Method vs Abstract Factory

  7. Creating Families of Related Objects

  8. UML Class Diagram

  9. Components of the Pattern

  10. Complete C# Console Application

  11. ASP.NET Core Example

  12. Real-World Use Cases

  13. Advantages

  14. Disadvantages

  15. Best Practices

  16. Common Mistakes

  17. Interview Questions

  18. Summary


Introduction

As applications grow, we often need to create multiple related objects that are designed to work together.

Consider a cross-platform UI application. The application supports:

  • Windows

  • macOS

Each platform has its own set of controls:

Windows

  • Windows Button

  • Windows TextBox

  • Windows CheckBox

macOS

  • Mac Button

  • Mac TextBox

  • Mac CheckBox

If client code creates these objects directly, it's easy to accidentally mix incompatible components—for example, using a Windows Button with a Mac TextBox.

The Abstract Factory Pattern solves this problem by ensuring that related objects are created together from the same product family.


What is the Abstract Factory Pattern?

Definition

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

Unlike the Factory Method Pattern, which creates one product, Abstract Factory creates multiple related products that are intended to work together.


Why Do We Need the Abstract Factory Pattern?

Suppose you're building an e-commerce application that integrates with multiple payment providers.

Each provider requires:

  • Payment Service

  • Refund Service

  • Invoice Service

For Stripe:

  • StripePayment

  • StripeRefund

  • StripeInvoice

For PayPal:

  • PayPalPayment

  • PayPalRefund

  • PayPalInvoice

Without an Abstract Factory, the application could accidentally combine a Stripe payment service with a PayPal refund service.

The Abstract Factory guarantees that all related services come from the same provider.


Problem Statement

Without the Abstract Factory Pattern:

IPayment payment = new StripePayment();

IRefund refund = new PaypalRefund();

This creates an inconsistent combination.

The application should either use all Stripe services or all PayPal services—not a mixture.


Real-World Analogy

Imagine buying furniture for your home.

You purchase a Living Room Set.

It includes:

  • Sofa

  • Chair

  • Coffee Table

All pieces belong to the same design family.

You don't usually buy:

  • Victorian Sofa

  • Modern Coffee Table

  • Industrial Chair

because they may not match.

Similarly, the Abstract Factory creates a complete, compatible family of related objects.


Factory Method vs Abstract Factory

FeatureFactory MethodAbstract Factory
CreatesOne productA family of related products
Pattern TypeCreationalCreational
Uses InheritanceYesOften combines interfaces and composition; implementations may use Factory Methods internally
ComplexityLowerHigher
Best ForSingle object creationCoordinated creation of related objects
ExampleCreate a Payment ServiceCreate Payment, Refund, and Invoice services together

Creating Families of Related Objects

Imagine two payment providers.

Stripe Family

  • StripePayment

  • StripeRefund

  • StripeInvoice

PayPal Family

  • PayPalPayment

  • PayPalRefund

  • PayPalInvoice

The Abstract Factory ensures that selecting the Stripe factory creates only Stripe services, while selecting the PayPal factory creates only PayPal services.


UML Class Diagram

                    +-------------------------------+
                    | IPaymentProviderFactory       |
                    +-------------------------------+
                    | + CreatePayment()             |
                    | + CreateRefund()              |
                    | + CreateInvoice()             |
                    +---------------^---------------+
                                    |
                  +-----------------+-----------------+
                  |                                   |
      +-------------------------+        +-------------------------+
      | StripeFactory           |        | PayPalFactory           |
      +-------------------------+        +-------------------------+
      | CreatePayment()         |        | CreatePayment()         |
      | CreateRefund()          |        | CreateRefund()          |
      | CreateInvoice()         |        | CreateInvoice()         |
      +-------------------------+        +-------------------------+

Components of the Pattern

Product Interfaces

public interface IPayment
{
    void Pay(decimal amount);
}

public interface IRefund
{
    void Refund(decimal amount);
}

public interface IInvoice
{
    void GenerateInvoice();
}

Stripe Products

public class StripePayment : IPayment
{
    public void Pay(decimal amount)
    {
        Console.WriteLine($"Stripe paid ₹{amount}");
    }
}

public class StripeRefund : IRefund
{
    public void Refund(decimal amount)
    {
        Console.WriteLine($"Stripe refunded ₹{amount}");
    }
}

public class StripeInvoice : IInvoice
{
    public void GenerateInvoice()
    {
        Console.WriteLine("Stripe invoice generated.");
    }
}

PayPal Products

public class PaypalPayment : IPayment
{
    public void Pay(decimal amount)
    {
        Console.WriteLine($"PayPal paid ₹{amount}");
    }
}

public class PaypalRefund : IRefund
{
    public void Refund(decimal amount)
    {
        Console.WriteLine($"PayPal refunded ₹{amount}");
    }
}

public class PaypalInvoice : IInvoice
{
    public void GenerateInvoice()
    {
        Console.WriteLine("PayPal invoice generated.");
    }
}

Abstract Factory

public interface IPaymentProviderFactory
{
    IPayment CreatePayment();

    IRefund CreateRefund();

    IInvoice CreateInvoice();
}

Concrete Factories

Stripe Factory

public class StripeFactory : IPaymentProviderFactory
{
    public IPayment CreatePayment() => new StripePayment();

    public IRefund CreateRefund() => new StripeRefund();

    public IInvoice CreateInvoice() => new StripeInvoice();
}

PayPal Factory

public class PaypalFactory : IPaymentProviderFactory
{
    public IPayment CreatePayment() => new PaypalPayment();

    public IRefund CreateRefund() => new PaypalRefund();

    public IInvoice CreateInvoice() => new PaypalInvoice();
}

Complete C# Console Application

class Program
{
    static void Main()
    {
        IPaymentProviderFactory factory = new StripeFactory();

        IPayment payment = factory.CreatePayment();
        IRefund refund = factory.CreateRefund();
        IInvoice invoice = factory.CreateInvoice();

        payment.Pay(2500);

        refund.Refund(500);

        invoice.GenerateInvoice();
    }
}

Output

Stripe paid ₹2500
Stripe refunded ₹500
Stripe invoice generated.

To switch providers, change only the factory:

IPaymentProviderFactory factory = new PaypalFactory();

The client code remains unchanged.


ASP.NET Core Example

The Abstract Factory Pattern integrates naturally with Dependency Injection.

Register Factories

builder.Services.AddScoped<StripeFactory>();
builder.Services.AddScoped<PaypalFactory>();

You can also register the abstract factory based on configuration:

builder.Services.AddScoped<IPaymentProviderFactory>(sp =>
{
    var config = sp.GetRequiredService<IConfiguration>();

    var provider = config["PaymentProvider"];

    return provider == "Stripe"
        ? new StripeFactory()
        : new PaypalFactory();
});

Inject into a Controller

public class PaymentController : ControllerBase
{
    private readonly IPaymentProviderFactory _factory;

    public PaymentController(IPaymentProviderFactory factory)
    {
        _factory = factory;
    }

    [HttpPost]
    public IActionResult Process()
    {
        var payment = _factory.CreatePayment();

        payment.Pay(5000);

        return Ok();
    }
}

Changing the provider in configuration changes the family of services without modifying controller code.


Real-World Use Cases

The Abstract Factory Pattern is useful when you need to create sets of related objects.

Examples include:

  • Cross-platform UI toolkits (Windows, macOS, Linux)

  • Payment provider integrations (Stripe, PayPal, Razorpay)

  • Database providers (SQL Server, PostgreSQL, Oracle)

  • Cloud providers (Azure, AWS, Google Cloud)

  • Logging frameworks with related components

  • Theme engines (Light Theme, Dark Theme)

  • Document generation suites (PDF, Excel, Word)


Advantages

  • Ensures related objects are compatible.

  • Promotes consistency across product families.

  • Supports the Open/Closed Principle.

  • Reduces coupling by programming to interfaces.

  • Simplifies switching between implementations.

  • Improves maintainability in large systems.


Disadvantages

  • Introduces more interfaces and classes.

  • Can increase complexity for small applications.

  • Adding a new product type (e.g., a new service in every family) requires updating the abstract factory interface and all concrete factories.

  • Requires careful design to avoid unnecessary abstraction.


Best Practices

  • Use Abstract Factory when products naturally belong together.

  • Keep product interfaces cohesive and focused.

  • Use Dependency Injection to supply factories.

  • Avoid embedding business logic inside factories.

  • Prefer configuration-driven selection of concrete factories in ASP.NET Core.


Common Mistakes

Mixing Product Families

Avoid creating products from different families in the same workflow unless your design explicitly supports it.

Using Abstract Factory for a Single Product

If only one object needs to be created, the Factory Method Pattern is usually simpler.

Overengineering

For small applications with one implementation, introducing an Abstract Factory may add unnecessary complexity.


Interview Questions

1. What is the Abstract Factory Pattern?

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


2. When should you use the Abstract Factory Pattern?

When you need to create groups of related objects that must work together, such as UI controls for different operating systems or services for different payment providers.


3. How is Abstract Factory different from Factory Method?

Factory Method creates a single product through a factory method. Abstract Factory creates multiple related products through a common factory interface.


4. Does Abstract Factory use Factory Method?

Often, yes. Many implementations use one or more factory methods inside concrete factories to create each product, although the exact implementation can vary.


5. What SOLID principles does Abstract Factory support?

  • Open/Closed Principle (OCP): New product families can be added by introducing new concrete factories.

  • Dependency Inversion Principle (DIP): Client code depends on abstract factory and product interfaces rather than concrete implementations.


6. What are some real-world examples?

  • Cross-platform UI libraries

  • Payment gateway integrations

  • Cloud provider SDK abstractions

  • Database provider factories

  • Theme engines


Summary

The Abstract Factory Pattern extends the idea of object creation by producing families of related objects rather than individual instances. It helps ensure compatibility between products, reduces coupling, and makes it easy to switch entire implementations—such as moving from one payment provider or UI theme to another—without changing client code.

While it introduces additional abstraction, it becomes invaluable in enterprise applications where consistency and extensibility are essential. Combined with Dependency Injection in ASP.NET Core, the Abstract Factory Pattern provides a clean and scalable way to manage complex object creation scenarios.


Coming Up Next

In Part 2.4, we'll explore the Builder Design Pattern, including:

  • What is the Builder Pattern?

  • Why constructors become difficult to maintain

  • Builder vs Constructor vs Object Initializer

  • Fluent Builder Pattern

  • Immutable Objects with Builder

  • UML Class Diagram

  • Complete C# Console Application

  • ASP.NET Core implementation

  • Real-world examples

  • Advantages and disadvantages

  • Best practices

  • Common mistakes

  • Interview questions

You'll learn how the Builder Pattern simplifies the creation of complex objects while improving readability, maintainability, and flexibility in modern C# applications.

No comments:

Don't Copy

Protected by Copyscape Online Plagiarism Checker