Back to Blog
architecture

Why the Interface Property Approach Might Be a Better Choice for Small Projects

February 25, 20267 min min read
Why the Interface Property Approach Might Be a Better Choice for Small Projects

Why the Interface Property Approach Might Be a Better Choice for Small Projects

"The factory pattern is powerful - but???"

The Factory Days

I used to be a Service Factory guy. Every time I needed to switch between payment providers, I'd build a factory, register it, inject it, and then politely ask it to hand me the right instance.

// Me, every single time 
var provider = _factory.GetProvider(PaymentProviderType.Khalti);

It felt powerful. It felt enterprise-y. It felt like I was running a big factory with a manager at the gate saying "Hey! Which worker do you want today?"

But then one day, staring at my three files — the factory interface, the factory implementation, and the enum switch — for a project that had literally four payment providers, I thought:

"Why do I need a factory manager when my workers already know who they are?"

The Nepali Payment Reality Check

Here in Nepal, you're dealing with eSewa, FonePay, Khalti — local heroes. And then there's Stripe & Paypal, sitting like a dream on the horizon.

Why Stripe? Well, our beloved Rastriya Swatantra Party (RSP — the ones who actually mentioned tech in thier Manifesto!) has promised to push for Stripe and PayPal integration in Nepal if they win the Election 2082 Falgun 21 (March 5, 2026).

Best of luck, RSP. Nepal's developers are watching.

So I am planning to add Stripe to my package — optimistically, politically, hopefully — at Nepal.Payments.Gateways | GitHub.

The Smart Factory

Then it hit me. What if instead of a dumb factory that I have to manually update every time I add a provider, I let the providers announce themselves?

Like a smart factory — you throw in raw material, it detects what it is, and produces accordingly. No middleman. No manager. No gate.

public enum PaymentProviderType { Stripe, eSewa, FonePay, Khalti }</p><p>public interface IPaymentProvider
{
    PaymentProviderType ProviderType { get; }   // "I know who I am."
    Task<PaymentResult> ChargeAsync(decimal amount, string currency);
}

Each provider self-identifies. No factory needed to tell the world what they are.

public class StripePaymentProvider : IPaymentProvider
{
    public PaymentProviderType ProviderType => PaymentProviderType.Stripe;   // Stripe — ready for Nepal, waiting for RSP to win 🤞</p><p>    public async Task<PaymentResult> ChargeAsync(decimal amount, string currency)
        => new PaymentResult { Success = true, Message = $"[Stripe] Charged {amount} {currency}" };
}
public class EsewaPaymentProvider : IPaymentProvider
{
    public PaymentProviderType ProviderType => PaymentProviderType.eSewa;</p><p>    public async Task<PaymentResult> ChargeAsync(decimal amount, string currency)
        => new PaymentResult { Success = true, Message = $"[eSewa] Charged {amount} NPR" };
}
public class FonePayPaymentProvider : IPaymentProvider
{
    public PaymentProviderType ProviderType => PaymentProviderType.FonePay;</p><p>    public async Task<PaymentResult> ChargeAsync(decimal amount, string currency)
        => new PaymentResult { Success = true, Message = $"[FonePay] Charged {amount} NPR" };
}
public class KhaltiPaymentProvider : IPaymentProvider
{
    public PaymentProviderType ProviderType => PaymentProviderType.Khalti;</p><p>    public async Task<PaymentResult> ChargeAsync(decimal amount, string currency)
        => new PaymentResult { Success = true, Message = $"[Khalti] Charged {amount} NPR" };
}

Register them all — just dump the raw materials in:

builder.Services.AddScoped<IPaymentProvider, StripePaymentProvider>();   // hopeful 🤞
builder.Services.AddScoped<IPaymentProvider, EsewaPaymentProvider>();
builder.Services.AddScoped<IPaymentProvider, FonePayPaymentProvider>();
builder.Services.AddScoped<IPaymentProvider, KhaltiPaymentProvider>();

The PaymentManager — the real smart factory:

public class PaymentManager(IEnumerable<IPaymentProvider> providers)
{
    public async Task<PaymentResult> ChargeAsync(PaymentProviderType type, decimal amount,     string currency = "NPR")
    {
        var provider = providers.FirstOrDefault(p => p.ProviderType == type)?? throw new NotSupportedException($"{type} provider not configured yet.");
        return await provider.ChargeAsync(amount, currency);
    }
}

Usage — clean, zero factory files:

await paymentManager.ChargeAsync(PaymentProviderType.Khalti, 500);
await paymentManager.ChargeAsync(PaymentProviderType.eSewa, 1000);
await paymentManager.ChargeAsync(PaymentProviderType.Stripe, 10, "USD"); // RSP wins → this works 🎉

The Moral of the Story

The old factory made me say: "Factory, give me a Stripe worker."

The interface property lets every worker say: "I am Stripe. Use me when needed."

Adding a new provider? Just write the class, register it — the PaymentManager detects it automatically. No factory file to open. No switch case to update. No manager to inform.

Yo nai ho — the dumb factory became a smart factory, and the smart factory needed no factory at all.

Check out Nepal.Payments.Gateways on NuGet — Stripe might be be there, patiently waiting for Falgun 21. 🗳️🙏

Share this article

Tags

.net factory pattern

Subscribe to Newsletter

Get notified about new articles