Interface Factory vs Abstract Factory: Untangling the Patterns with Real-World Payment Gateway
In the world of software design, the goal is always the same: flexibility, testability, and separation of concerns. While the Factory Pattern is widely known, developers often get confused between Interface Factory and Abstract Factory.
Let’s decode both, with real-world use cases, and walk through a payment gateway integration scenario.
What Are We Solving?
You’re building a .NET application that supports multiple payment gateways: Razorpay, Stripe, and PayPal.
Your goal:
Abstract the implementation
Avoid
switch
orif-else
statementsAllow for future extensions (like adding PhonePe)
Keep things testable
Traditional Factory Pattern (Basic Implementation)
A typical Factory Pattern uses a central factory to decide which object to return.
public class PaymentFactory
{
public static IPaymentGateway GetGateway(string gateway)
{
switch (gateway)
{
case "Stripe": return new StripeGateway();
case "Razorpay": return new RazorpayGateway();
default: throw new NotSupportedException("Unsupported gateway");
}
}
}
Problem:
https://dotnetfullstackdev.medium.com/
Hard to unit test.
Difficult to extend.
You need to modify the factory every time you add a new gateway (violates Open-Closed Principle).
Enter Interface Factory Pattern
An Interface Factory delegates the creation to a separate factory per implementation.
Step-by-Step Design
public interface IPaymentGateway
{
void ProcessPayment(decimal amount);
}
public interface IPaymentGatewayFactory
{
IPaymentGateway Create();
}
Now for each gateway:
public class StripeGateway : IPaymentGateway
{
public void ProcessPayment(decimal amount)
{
Console.WriteLine($"Stripe paid: {amount}");
}
}
public class StripeGatewayFactory : IPaymentGatewayFactory
{
public IPaymentGateway Create() => new StripeGateway();
}
Register factories in your DI container:
services.AddTransient<IPaymentGatewayFactory, StripeGatewayFactory>();
Now your controller just asks for a factory:
public class PaymentService
{
private readonly IPaymentGatewayFactory _factory;
public PaymentService(IPaymentGatewayFactory factory)
{
_factory = factory;
}
public void Pay(decimal amount)
{
var gateway = _factory.Create();
gateway.ProcessPayment(amount);
}
}
Benefits:
Each factory class is testable.
No central switch logic.
More SOLID-friendly.
Abstract Factory Pattern
Abstract Factory produces families of related objects.
🔧 In our context, imagine you need:
Payment Gateway (for money)
Refund Processor (for refunds)
Invoice Generator
Instead of having individual factories, you bundle them together.
public interface IPaymentGatewayFactory
{
IPaymentGateway CreatePaymentGateway();
IRefundProcessor CreateRefundProcessor();
}
Now Stripe implementation looks like:
public class StripeFactory : IPaymentGatewayFactory
{
public IPaymentGateway CreatePaymentGateway() => new StripeGateway();
public IRefundProcessor CreateRefundProcessor() => new StripeRefund();
}
This is powerful when you want to switch entire ecosystems (like Stripe or Razorpay) that work in unison — a related family of objects.
Use When:
You’re working with multiple related components.
You want to isolate the environment (Stripe vs Razorpay)
Conclusion
If you're just injecting one type of service, go for Interface Factory.
If you're managing groups of services that work together, use Abstract Factory.
Both avoid the trap of giant switch
statements and bring flexibility, testability, and maintainability to your .NET applications.
Pro Tip
"Design patterns don’t add code—they add clarity when used rightly. Interface Factory is your MVP, Abstract Factory is your Coach."
Let your architecture grow without fear. Start small, refactor clean, and inject with precision.