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.

Factory Method Design Pattern

Part 2.2 – Factory Method Design Pattern

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

Pattern Type: Creational Design Pattern

Difficulty: ⭐⭐☆☆☆ (Beginner to Intermediate)

Prerequisites: OOP Concepts, Interfaces, Inheritance, Polymorphism


Table of Contents

  1. Introduction

  2. What is Factory Method Pattern?

  3. Why Do We Need Factory Method?

  4. The Problem with new

  5. Real-World Analogy

  6. Factory Method Structure

  7. UML Class Diagram

  8. Components of the Pattern

  9. Step-by-Step C# Example

  10. ASP.NET Core Example

  11. Real-World Use Cases

  12. Advantages

  13. Disadvantages

  14. Factory Method vs Simple Factory

  15. Best Practices

  16. Common Mistakes

  17. Interview Questions

  18. Summary


Introduction

One of the most common practices among beginner developers is creating objects directly using the new keyword.

Payment payment = new CreditCardPayment();

At first glance, this looks perfectly fine. However, as applications grow, direct object creation introduces tight coupling, making the system harder to maintain, extend, and test.

Imagine an e-commerce application that initially supports only Credit Card payments. Later, business requirements change, and the application must support PayPal, UPI, Net Banking, and Cryptocurrency payments.

If every part of the application directly creates payment objects using new, every location must be updated when a new payment type is introduced. This increases maintenance effort and the risk of bugs.

The Factory Method Pattern addresses this by separating object creation from object usage.


What is the Factory Method Pattern?

Definition

The Factory Method Pattern defines an interface (or abstract method) for creating objects, allowing subclasses or specialized factory classes to decide which concrete object to instantiate.

In simple terms, instead of creating objects directly, you ask a factory to create them.

Instead of writing:

new CreditCardPayment();

you write:

factory.CreatePayment();

The client no longer needs to know the concrete class being created.


Why Do We Need the Factory Method Pattern?

Without the Factory Method Pattern:

  • Business logic is tightly coupled to concrete classes.

  • Adding new implementations requires modifying existing code.

  • Testing becomes more difficult because dependencies are hard-coded.

  • The application violates the Open/Closed Principle (OCP).

With the Factory Method Pattern:

  • Object creation is centralized.

  • New implementations can be added with minimal changes.

  • Client code depends on abstractions rather than concrete implementations.

  • The system becomes easier to maintain and extend.


The Problem with new

Consider an online shopping application.

public class PaymentProcessor
{
    public void Process()
    {
        CreditCardPayment payment = new CreditCardPayment();
        payment.Pay();
    }
}

The PaymentProcessor is directly dependent on CreditCardPayment.

If support for PayPal is added, the class must be modified:

public void Process(string type)
{
    if(type == "Card")
        new CreditCardPayment();

    else if(type == "PayPal")
        new PaypalPayment();

    else if(type == "UPI")
        new UpiPayment();
}

This approach quickly becomes cluttered and difficult to maintain.


Real-World Analogy

Imagine ordering coffee at a café.

You don't walk into the kitchen and prepare the coffee yourself.

Instead, you tell the barista what you want.

The barista decides:

  • Which ingredients to use.

  • Which machine to operate.

  • How the coffee is prepared.

You simply receive the finished coffee.

In this analogy:

  • Customer → Client

  • Barista → Factory

  • Coffee → Product

The customer doesn't know how the coffee is made—they only request a type of coffee.


Factory Method Structure

The pattern consists of four main participants:

  • Product – Defines the interface.

  • Concrete Product – Implements the product.

  • Creator (Factory) – Declares the factory method.

  • Concrete Creator – Implements the factory method to return a specific product.


UML Class Diagram

                   +----------------------+
                   |      IPayment        |
                   +----------------------+
                   | + Pay()             |
                   +----------^-----------+
                              |
        +---------------------+---------------------+
        |                                           |
+----------------------+                 +----------------------+
| CreditCardPayment    |                 | PaypalPayment        |
+----------------------+                 +----------------------+
| + Pay()             |                 | + Pay()             |
+----------------------+                 +----------------------+

                    +----------------------+
                    | PaymentFactory       |
                    +----------------------+
                    | + CreatePayment()    |
                    +----------^-----------+
                               |
        +----------------------+----------------------+
        |                                             |
+------------------------+              +------------------------+
| CreditCardFactory      |              | PaypalFactory          |
+------------------------+              +------------------------+
| CreatePayment()        |              | CreatePayment()        |
+------------------------+              +------------------------+

Components of the Pattern

Product

Defines the common interface.

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

Concrete Products

public class CreditCardPayment : IPayment
{
    public void Pay(decimal amount)
    {
        Console.WriteLine($"Paid ₹{amount} using Credit Card.");
    }
}

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

Creator (Factory)

public abstract class PaymentFactory
{
    public abstract IPayment CreatePayment();
}

Concrete Factories

public class CreditCardFactory : PaymentFactory
{
    public override IPayment CreatePayment()
    {
        return new CreditCardPayment();
    }
}

public class PaypalFactory : PaymentFactory
{
    public override IPayment CreatePayment()
    {
        return new PaypalPayment();
    }
}

Step-by-Step C# Example

Client Code

class Program
{
    static void Main()
    {
        PaymentFactory factory = new CreditCardFactory();

        IPayment payment = factory.CreatePayment();

        payment.Pay(2500);
    }
}

Output

Paid ₹2500 using Credit Card.

If you want to switch to PayPal:

PaymentFactory factory = new PaypalFactory();

No other client code changes are required.


ASP.NET Core Example

In ASP.NET Core, the Factory Method Pattern is often combined with Dependency Injection.

Product Interface

public interface INotificationService
{
    void Send(string message);
}

Concrete Products

public class EmailNotificationService : INotificationService
{
    public void Send(string message)
    {
        Console.WriteLine($"Email: {message}");
    }
}

public class SmsNotificationService : INotificationService
{
    public void Send(string message)
    {
        Console.WriteLine($"SMS: {message}");
    }
}

Factory

public interface INotificationFactory
{
    INotificationService Create(string notificationType);
}

public class NotificationFactory : INotificationFactory
{
    public INotificationService Create(string notificationType)
    {
        return notificationType switch
        {
            "Email" => new EmailNotificationService(),
            "SMS" => new SmsNotificationService(),
            _ => throw new ArgumentException("Unsupported notification type")
        };
    }
}

Register Services

builder.Services.AddSingleton<INotificationFactory, NotificationFactory>();

Controller

public class NotificationController : ControllerBase
{
    private readonly INotificationFactory _factory;

    public NotificationController(INotificationFactory factory)
    {
        _factory = factory;
    }

    [HttpPost]
    public IActionResult Send(string type, string message)
    {
        var service = _factory.Create(type);
        service.Send(message);

        return Ok();
    }
}

Real-World Use Cases

The Factory Method Pattern is commonly used when the exact type of object depends on runtime conditions.

Examples include:

  • Payment gateways (Credit Card, UPI, PayPal, Stripe)

  • Notification systems (Email, SMS, Push)

  • Document generators (PDF, Excel, Word)

  • Database providers (SQL Server, PostgreSQL, MySQL)

  • Cloud storage providers (Azure Blob Storage, AWS S3, Google Cloud Storage)

  • Report exporters (CSV, PDF, Excel)


Advantages

  • Promotes loose coupling by depending on abstractions.

  • Supports the Open/Closed Principle.

  • Centralizes object creation logic.

  • Makes testing easier by allowing mock implementations.

  • Simplifies adding new product types without changing client code.

  • Encourages cleaner and more maintainable code.


Disadvantages

  • Introduces additional classes, increasing the size of the codebase.

  • Can feel excessive for very small or simple applications.

  • Requires developers to understand abstraction and inheritance.

  • Poorly designed factories can become overly complex.


Factory Method vs Simple Factory

FeatureSimple FactoryFactory Method
Standard GoF Pattern❌ No✅ Yes
Uses Inheritance❌ Usually not✅ Yes
Uses PolymorphismLimitedExtensive
ExtensibleLimitedHigh
Open/Closed PrincipleOften violated when adding new productsBetter supported
ComplexityLowerHigher
Best ForSmall applicationsMedium to large applications

Simple Factory centralizes creation in a single class, often using if or switch statements. It's straightforward but can require modification when new product types are added.

Factory Method relies on inheritance and polymorphism. New product types are introduced by creating new factory subclasses rather than modifying existing factory logic, aligning better with the Open/Closed Principle.


Best Practices

  • Program to interfaces, not concrete implementations.

  • Keep factories focused on object creation only.

  • Avoid placing business logic inside factories.

  • Use Dependency Injection to supply factories where appropriate.

  • Consider the Abstract Factory Pattern when multiple related objects must be created together.


Common Mistakes

Overusing the Pattern

Not every object needs a factory. For simple data objects or classes with trivial construction, using new directly is often perfectly acceptable.

Mixing Business Logic into Factories

Factories should create objects, not perform business operations.

Returning Concrete Types

Return interfaces or abstract base classes from factory methods whenever possible. This keeps client code decoupled from specific implementations.


Interview Questions

1. What is the Factory Method Pattern?

It is a creational design pattern that defines an interface for creating objects while allowing subclasses or specialized factories to determine which concrete object to instantiate.


2. What problem does the Factory Method Pattern solve?

It removes direct dependencies on concrete classes, making code more maintainable, extensible, and testable.


3. How does the Factory Method Pattern support the Open/Closed Principle?

New product types can be introduced by adding new concrete factories and products without modifying existing client code.


4. What is the difference between Factory Method and Abstract Factory?

Factory Method creates one product through a factory method. Abstract Factory creates families of related products using a factory interface with multiple creation methods.


5. What is the difference between Factory Method and Simple Factory?

A Simple Factory usually uses conditional logic in one class to create products. Factory Method uses inheritance and polymorphism, making it easier to extend without modifying existing code.


6. Where is the Factory Method Pattern commonly used?

  • Payment processing

  • Notification services

  • Database provider selection

  • Report generation

  • Cloud storage integration

  • Plugin architectures


Summary

The Factory Method Pattern is a foundational creational design pattern that separates object creation from object usage. By introducing factories and programming to interfaces, it reduces coupling, improves extensibility, and aligns with SOLID principles—particularly the Open/Closed Principle and Dependency Inversion Principle.

While direct use of new is appropriate in many simple scenarios, Factory Method becomes valuable when object creation depends on runtime conditions, configuration, or evolving business requirements. Combined with ASP.NET Core's Dependency Injection framework, it enables flexible, testable, and maintainable application architectures.


Coming Up Next

In Part 2.3, we'll explore the Abstract Factory Design Pattern, including:

  • What is the Abstract Factory Pattern?

  • Factory Method vs. Abstract Factory

  • Creating families of related objects

  • UML Class Diagram

  • Complete C# Console Application

  • ASP.NET Core implementation

  • Real-world examples

  • Advantages and disadvantages

  • Common mistakes

  • Interview questions and best practices

You'll see how Abstract Factory builds upon Factory Method to create entire groups of related objects while maintaining consistency across product families.

Abstract-factory-design-pattern


Don't Copy

Protected by Copyscape Online Plagiarism Checker