The Factory Pattern is a creational design pattern that provides a centralized place to creating an objects without exposing the object creation logic to the client.

The client requests a factory for an object, and the factory decides which concrete class to instantiate. Instead of using new keyword to create a object, object creation will be delegated to the factory.

When the system grows, one of the first design problems we face is object creation. Tight coupling between object creation and object usage makes code hard to extend, test, and maintain. That’s where the Factory Pattern becomes extremely useful.

Prerequisites

Source Code

The source code used in the example is publicly available on github: Link

Why do we need the factory pattern?

Let’s take a simple example

var payment = new CreditCardPayment();
payment.Pay();

The above code example has the following issues:

Now imagine adding other payment methods like DebitCard, PayPal etc. we will be ended up having else-if ladder or switch statement accross the codebase.

Solution With Factory Pattern

The Factory Pattern will helps to:

Step-by-Step implementation with real world example

Let’s consider the real-world payment processing scenario and combine it with the dependency injection to make a program loosely coupled.

Step 1: Create a Payment Interface

public interface IPaymentMethod
{
    string Method { get; }
    void Pay(decimal amount);
}

Step 2: Write concrete implementation for Credit Card, Debit Card, PayPal

Credit Card Payment Class

public class CreditCardPayment : IPaymentMethod
{
    public string Method => "credit";

    public void Pay(decimal amount)
    {
        Console.WriteLine($"Paid {amount} using Credit Card");
    }
}

Debit Card Payment Class

public class DebitCardPayment : IPaymentMethod
{
    public string Method => "debit";

    public void Pay(decimal amount)
    {
        Console.WriteLine($"Paid {amount} using Debit Card");
    }
}

PayPal Payment Class

public class PaypalPayment : IPaymentMethod
{
    public string Method => "paypal";

    public void Pay(decimal amount)
    {
        Console.WriteLine($"Paid {amount} using PayPal");
    }
}

Step 3: Crate a Factory Interface IPaymentFactory

public interface IPaymentFactory
{
    IPaymentMethod Create(string method);
}

Step 4: Factory Implementation PaymentFactory (with no Coupling)

using System;
using System.Collections.Generic;
using System.Linq;

public class PaymentFactory : IPaymentFactory
{
    private readonly IDictionary<string, IPaymentMethod> _paymentMethods;

    public PaymentFactory(IEnumerable<IPaymentMethod> paymentMethods)
    {
        _paymentMethods = paymentMethods.ToDictionary(
            p => p.Method,
            p => p,
            StringComparer.OrdinalIgnoreCase);
    }

    public IPaymentMethod Create(string method)
    {
        if (_paymentMethods.TryGetValue(method, out var payment))
            return payment;

        throw new NotSupportedException($"Payment method '{method}' is not supported");
    }
}

Step 5: Program.cs

using FactoryPatternWithDI.Factories;
using FactoryPatternWithDI.Payments;
using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();

// Register payment implementations
services.AddTransient<IPaymentMethod, CreditCardPayment>();
services.AddTransient<IPaymentMethod, DebitCardPayment>();
services.AddTransient<IPaymentMethod, PaypalPayment>();

// Register factory
services.AddSingleton<IPaymentFactory, PaymentFactory>();

var provider = services.BuildServiceProvider();

var factory = provider.GetRequiredService<IPaymentFactory>();

Console.WriteLine("Choose payment method: credit / debit / paypal");
var method = Console.ReadLine();

var payment = factory.Create(method);
payment.Pay(1500);

Console.ReadLine();

Conclusion

In the above example the client code Program.cs never creates a concrete payment object using a new keyword. Instead, it requests a payment method from IPaymentFactory. Each of the payment types like CreditCardPayment, DebitCardPayment, PaypalPayment implements the common IPaymentMethod and self-identifies using a method key.

Let’s analyze the above implementation via design-principle perspective: