Push the decision of which concrete class to build into a method that subclasses can override.
When a function has to choose between `new EmailNotifier()`, `new SmsNotifier()`, `new PushNotifier()` based on a condition, that condition — and the list of every concrete class — gets duplicated everywhere the object is created. Add a fourth channel and you're hunting down every `if/else` chain in the codebase.
A creator class declares a factory method. Concrete creators override it to return a specific product. Client code calls the factory method and works only against the product's shared interface — it never names the concrete class directly.
Client code only ever calls `create()`. Which class gets instantiated depends on which creator you picked once, far from the call site that actually sends the message.
class Notifier {
create() {
throw new Error("Subclasses must implement create()");
}
notify(message) {
const channel = this.create();
channel.send(message);
}
}
class EmailNotifier extends Notifier {
create() {
return { send: (msg) => console.log(`[email] ${msg}`) };
}
}
class SmsNotifier extends Notifier {
create() {
return { send: (msg) => console.log(`[sms] ${msg}`) };
}
}
function notifierFor(channel) {
const registry = { email: EmailNotifier, sms: SmsNotifier };
const NotifierClass = registry[channel];
if (!NotifierClass) throw new Error(`Unknown channel: ${channel}`);
return new NotifierClass();
}
notifierFor("email").notify("Build passed");
notifierFor("sms").notify("Build passed");