Skip to content

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.

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 gets
const processor = PaymentProcessorFactory.create('stripe');
await processor.charge(99.99);

Define an interface for creating objects, but let subclasses decide which class to instantiate:

// Product interface
public interface INotification
{
void Send(string recipient, string message);
}
// Concrete products
public 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 method
public abstract class NotificationService
{
protected abstract INotification CreateNotification();
public void Notify(string recipient, string message)
{
var notification = CreateNotification();
notification.Send(recipient, message);
}
}
// Concrete creators
public class EmailNotificationService : NotificationService
{
protected override INotification CreateNotification() =>
new EmailNotification();
}
public class SmsNotificationService : NotificationService
{
protected override INotification CreateNotification() =>
new SmsNotification();
}
// Usage
NotificationService service = new EmailNotificationService();
service.Notify("alice@example.com", "Your order has shipped");

Creates families of related objects:

// Abstract factory interface
interface UIFactory {
createButton(): Button;
createInput(): Input;
createModal(): Modal;
}
// Concrete factories for each theme
class 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 classes
class Application {
constructor(private factory: UIFactory) {}
render() {
const button = this.factory.createButton();
const input = this.factory.createInput();
button.render();
input.render();
}
}
// Choose factory at startup
const factory = isDarkMode ? new DarkThemeFactory() : new LightThemeFactory();
const app = new Application(factory);

In .NET, the DI container is itself an Abstract Factory:

// Register multiple implementations
services.AddTransient<IPaymentProcessor, StripeProcessor>();
services.AddKeyedTransient<IPaymentProcessor, PayPalProcessor>("paypal");
// Or use a factory delegate
services.AddTransient<Func<string, IPaymentProcessor>>(provider => key => key switch
{
"stripe" => provider.GetRequiredService<StripeProcessor>(),
"paypal" => provider.GetRequiredService<PayPalProcessor>(),
_ => throw new ArgumentException($"Unknown payment method: {key}")
});
  • 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