Factory Method
Define an interface for creating an object, but let subclasses decide which class to instantiate. Decouple object creation from usage so new types can be added without touching client code.
The Problem
Imagine you’re building a logistics app. Version 1 only ships by truck — so your codebase is full of new Truck() calls. Six months later, you need to add sea freight. Now you have to hunt down every new Truck() and wrap it in an if/else. Add air freight later and it gets worse. Your core business logic is tangled up with object construction.
// Before: tight coupling
function createShipment(type: string) {
if (type === 'truck') return new Truck();
if (type === 'ship') return new Ship();
// every new transport type = touching this function
}
The real problem: the code that uses a transport shouldn’t need to know how to build it.
The Solution
The Factory Method pattern introduces a factory method — a method whose sole job is to create objects. The base class declares the method but delegates the “which class to instantiate” decision to subclasses.
Real-World Analogy
A staffing agency places workers in companies. The agency (Creator) guarantees the worker will implement a work() method, but each department (Concrete Creator) decides which type of worker to hire — developer, designer, manager. The client company never needs to interview candidates directly.
TypeScript Example
// Product interface
interface Transport {
deliver(cargo: string): string;
}
// Concrete products
class Truck implements Transport {
deliver(cargo: string): string {
return `Delivering "${cargo}" by road in a truck.`;
}
}
class Ship implements Transport {
deliver(cargo: string): string {
return `Delivering "${cargo}" across the sea by ship.`;
}
}
// Creator (abstract)
abstract class Logistics {
// The factory method
abstract createTransport(): Transport;
// Business logic that uses the product
planDelivery(cargo: string): string {
const transport = this.createTransport();
return transport.deliver(cargo);
}
}
// Concrete creators
class RoadLogistics extends Logistics {
createTransport(): Transport {
return new Truck();
}
}
class SeaLogistics extends Logistics {
createTransport(): Transport {
return new Ship();
}
}
// Client code — never mentions Truck or Ship directly
function clientCode(logistics: Logistics, cargo: string) {
console.log(logistics.planDelivery(cargo));
}
const road = new RoadLogistics();
clientCode(road, 'Electronics');
// → Delivering "Electronics" by road in a truck.
const sea = new SeaLogistics();
clientCode(sea, 'Heavy machinery');
// → Delivering "Heavy machinery" across the sea by ship.
// Adding AirLogistics later requires zero changes to client code
class AirLogistics extends Logistics {
createTransport(): Transport {
return new Plane(); // just add a new class + creator
}
}
When to Use
- You don’t know ahead of time which class you’ll need to instantiate
- You want to give library/framework users a way to extend its internal components
- You need to reuse or share objects from a pool rather than creating new ones each time
- The type of object depends on configuration, environment, or user input
Pros
- Eliminates tight coupling between creator and concrete product classes
- Obeys the Open/Closed Principle — add new product types without modifying existing creators
- Centralises object creation, making it easy to swap implementations
- Product-creation logic lives in one dedicated place
Cons
- Requires a new subclass for each product type — can grow the class hierarchy
- Slightly more indirection than a direct
newcall — may feel like over-engineering for simple cases
Related Patterns
- Abstract Factory groups multiple factory methods into a family
- Template Method uses the same subclassing mechanism for algorithm steps
- Prototype can replace Factory Method when you want to copy an existing object rather than create a new one