A Factory is an object whose only job is to construct other objects. The caller asks for a `Notifier` for `userPrefs`; the factory decides whether to return an `EmailNotifier`, `SmsNotifier`, or `PushNotifier`. The caller never sees `new`. This decouples *what to build* from *which concrete class to build* — letting you add new variants without touching any caller.
Whenever construction logic is non-trivial, varies by input, or you want to swap concrete implementations without touching the caller. Factories are foundational for plugin architectures, dependency injection, and any system where you might add new types later (payment methods, storage backends, message formats).
Try it
The shape of a factory
interface Notifier { send(msg: string): void; }
class NotifierFactory {
static create(channel: 'email' | 'sms' | 'push'): Notifier {
switch (channel) {
case 'email': return new EmailNotifier();
case 'sms': return new SmsNotifier();
case 'push': return new PushNotifier();
}
}
}
// Caller — no `new` of a concrete class anywhere.
const notifier = NotifierFactory.create(user.preferredChannel);
notifier.send('Order confirmed');
Why this matters in system design
In service architectures, factories show up as transport abstractions and provider abstractions:
- A
StorageClient.create(config)returningS3Storage,AzureBlobStorage, orGcsStoragebased on environment. - A
PaymentGateway.for(merchant)returning the right Stripe / Adyen / Razorpay client. - A message-broker abstraction that returns Kafka, RabbitMQ, or in-memory based on config — production uses Kafka, tests use in-memory.
Factory Method vs Abstract Factory
Trade-offs
Pros: Decouples construction from use; supports Open/Closed; centralizes the where-to-add-new-types decision.
Cons: Indirection has a cognitive cost; switch-on-type factories can drift toward god-objects if you keep stuffing variants in. When that happens, split into a registry (config maps type names to constructors) or move to DI.
Comments 0
Discuss this page. Markdown supported. Be kind.