Factory Method Design Pattern
I had a notification system that started with just email. Then someone wanted SMS. Then push notifications. Every time I added a channel, I had to change ...
23 Mar 2024

I had a notification system that started with just email. Then someone wanted SMS. Then push notifications. Every time I added a channel, I had to change the code that created the notification sender and the code that used it. Touching two places for every new type was a recipe for bugs.
The Factory Method pattern separates what gets created from how it gets used. You define an interface for creating objects, but let subclasses decide which class to instantiate. The client code works with the abstract type and never mentions a concrete class.
Think of it like a car dealership. You walk in and say "I want a vehicle." The dealership decides whether to hand you a sedan, SUV, or truck based on your needs. You drive it the same way regardless.
class Vehicle {
constructor() {
if (this.constructor === Vehicle) {
throw new Error("Vehicle cannot be instantiated directly.");
}
}
display() {
console.log("This is a generic vehicle.");
}
}
class Car extends Vehicle {
display() {
console.log("This is a car.");
}
}
class Motorcycle extends Vehicle {
display() {
console.log("This is a motorcycle.");
}
}
class VehicleFactory {
createVehicle() {
throw new Error("createVehicle method must be implemented.");
}
}
class CarFactory extends VehicleFactory {
createVehicle() {
return new Car();
}
}
class MotorcycleFactory extends VehicleFactory {
createVehicle() {
return new Motorcycle();
}
}
const carFactory = new CarFactory();
const car = carFactory.createVehicle();
car.display(); // This is a car.
const motorcycleFactory = new MotorcycleFactory();
const motorcycle = motorcycleFactory.createVehicle();
motorcycle.display(); // This is a motorcycle.
Vehicle is the abstract product. Car and Motorcycle are concrete products. VehicleFactory defines the factory method createVehicle(). Each concrete factory (CarFactory, MotorcycleFactory) overrides it to produce the right type.
The calling code works with VehicleFactory and Vehicle. It never mentions Car or Motorcycle. Adding a Truck means writing TruckFactory — no changes to existing code.
The benefit: Open/Closed Principle in action. New types don't require modifying existing code. The creation logic is centralized and testable. You can swap implementations at runtime by swapping the factory.
The cost: More classes. For every product type, you need a factory class. If you only have one or two types that rarely change, a simple if/else or a map of constructors is simpler. Factory Method earns its keep when the number of types grows or when creation logic is non-trivial.
I reach for Factory Method when I need to decouple object creation from usage and when the set of types is expected to grow. For static sets, a plain factory function with a switch statement does the job with less ceremony.