Factory Pattern
Factory Pattern
Section titled “Factory Pattern”Factory patterns centralise and encapsulate object creation. Instead of calling new ConcreteClass() throughout your code, you call a factory that returns the right object.
Simple Factory
Section titled “Simple Factory”Not a GoF pattern, but commonly used. A static method or class that creates objects:
type PaymentMethod = 'stripe' | 'paypal' | 'bank-transfer';
interface PaymentProcessor { charge(amount: number): Promise<void>;}
class PaymentProcessorFactory { static create(method: PaymentMethod): PaymentProcessor { switch (method) { case 'stripe': return new StripeProcessor(); case 'paypal': return new PayPalProcessor(); case 'bank-transfer': return new BankTransferProcessor(); default: throw new Error(`Unsupported payment method: ${method}`); } }}
// Usage — caller doesn't know which class it getsconst processor = PaymentProcessorFactory.create('stripe');await processor.charge(99.99);Factory Method (GoF)
Section titled “Factory Method (GoF)”Define an interface for creating objects, but let subclasses decide which class to instantiate:
// Product interfacepublic interface INotification{ void Send(string recipient, string message);}
// Concrete productspublic class EmailNotification : INotification{ public void Send(string recipient, string message) => Console.WriteLine($"Email to {recipient}: {message}");}
public class SmsNotification : INotification{ public void Send(string recipient, string message) => Console.WriteLine($"SMS to {recipient}: {message}");}
// Creator — defines the factory methodpublic abstract class NotificationService{ protected abstract INotification CreateNotification();
public void Notify(string recipient, string message) { var notification = CreateNotification(); notification.Send(recipient, message); }}
// Concrete creatorspublic class EmailNotificationService : NotificationService{ protected override INotification CreateNotification() => new EmailNotification();}
public class SmsNotificationService : NotificationService{ protected override INotification CreateNotification() => new SmsNotification();}
// UsageNotificationService service = new EmailNotificationService();service.Notify("alice@example.com", "Your order has shipped");Abstract Factory
Section titled “Abstract Factory”Creates families of related objects:
// Abstract factory interfaceinterface UIFactory { createButton(): Button; createInput(): Input; createModal(): Modal;}
// Concrete factories for each themeclass LightThemeFactory implements UIFactory { createButton() { return new LightButton(); } createInput() { return new LightInput(); } createModal() { return new LightModal(); }}
class DarkThemeFactory implements UIFactory { createButton() { return new DarkButton(); } createInput() { return new DarkInput(); } createModal() { return new DarkModal(); }}
// Client uses the factory without knowing the concrete classesclass Application { constructor(private factory: UIFactory) {}
render() { const button = this.factory.createButton(); const input = this.factory.createInput(); button.render(); input.render(); }}
// Choose factory at startupconst factory = isDarkMode ? new DarkThemeFactory() : new LightThemeFactory();const app = new Application(factory);Factory with Dependency Injection
Section titled “Factory with Dependency Injection”In .NET, the DI container is itself an Abstract Factory:
// Register multiple implementationsservices.AddTransient<IPaymentProcessor, StripeProcessor>();services.AddKeyedTransient<IPaymentProcessor, PayPalProcessor>("paypal");
// Or use a factory delegateservices.AddTransient<Func<string, IPaymentProcessor>>(provider => key => key switch{ "stripe" => provider.GetRequiredService<StripeProcessor>(), "paypal" => provider.GetRequiredService<PayPalProcessor>(), _ => throw new ArgumentException($"Unknown payment method: {key}")});When to Use Factory Patterns
Section titled “When to Use Factory Patterns”- The exact type of object to create depends on runtime data
- Object creation involves complex logic you want to centralise
- You want to decouple client code from concrete implementations
- You need to swap implementations for testing or feature flags