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:
Tight coupling with a concrete class
Difficult to extend (new payment types require code changes everywhere)
Violates Open/Closed Principle
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:
Single Responsibility Principle: Here payment classes handles the payment logic, the factory handles the selection and client handles the orchestration.
Open/Close Principle: New payment method can be added without changing existing factory or client implementation code. We can add StripePayment without touching Program.cs or PaymentFactory
Dependency Inversion Principle: Here high level modules like PaymentFactory and Program.cs is dependent on the abstaction not on the concrete implementation class like CreditCardPayment, DebitCardPayment or PaypalPayment classes.