FACTORY METHOD / QUICK REFERENCE
One creation hook.
Many possible products.
A creator defines a workflow that uses a product interface. Subclasses override the method that supplies the concrete product.
- What is created?
- A vehicle object: a Truck, Ship or Plane.
- What stays shared?
- Check the destination, create transport, then deliver the package.
- Who chooses the vehicle?
- RoadDelivery creates a Truck. SeaDelivery creates a Ship. Each overrides the same creation method.
The roles
- Product · Transport
- The interface the shared workflow uses: deliver(shipment) returns a DeliveryReceipt.
- Concrete product · Ship
- Implements the interface by simulating delivery over water. Truck drives on roads; Plane flies through the air.
- Creator · DeliveryService
- Owns dispatch() and declares createTransport(). Its workflow uses the product without naming a concrete class.
- Concrete creator · SeaDelivery
- Overrides createTransport() to supply Ship. It inherits dispatch().
Follow the creation hook
Product contract · TypeScript
export type Shipment = Readonly<{ destination: string }>;
export type DeliveryReceipt = Readonly<{
vehicle: string;
destination: string;
message: string;
}>;
export interface Transport {
deliver(shipment: Shipment): DeliveryReceipt;
}Creator and shared workflow · TypeScript
export abstract class DeliveryService {
protected abstract createTransport(record: DeliveryRecorder): Transport;
dispatch(
input: Shipment,
record: DeliveryRecorder = ignore,
): DeliveryReceipt {
const shipment = validateShipment(input);
record({
phase: "validate",
message: "The shared dispatch workflow checks the destination.",
});
record({
phase: "hook",
message:
"dispatch() reaches this.createTransport(). Which implementation will run?",
});
const transport = this.createTransport(record);
record({
phase: "return",
message:
"The created object returns to dispatch() as a Transport. The workflow resumes here.",
});
const receipt = transport.deliver(shipment);
record({ phase: "deliver", message: receipt.message });
record({
phase: "receipt",
message: "Delivery simulated. The shared workflow returns the receipt.",
});
return receipt;
}
}Concrete creators · TypeScript
export class RoadDelivery extends DeliveryService {
protected createTransport(record: DeliveryRecorder = ignore): Transport {
record({
phase: "override",
message:
"This is a RoadDelivery instance, so its createTransport() override runs.",
});
const transport = new Truck();
record({
phase: "create",
message:
"The override constructs a Truck object. It implements Transport.",
});
return transport;
}
}
export class SeaDelivery extends DeliveryService {
protected createTransport(record: DeliveryRecorder = ignore): Transport {
record({
phase: "override",
message:
"This is a SeaDelivery instance, so its createTransport() override runs.",
});
const transport = new Ship();
record({
phase: "create",
message:
"The override constructs a Ship object. It implements Transport.",
});
return transport;
}
}
export class AirDelivery extends DeliveryService {
protected createTransport(record: DeliveryRecorder = ignore): Transport {
record({
phase: "override",
message:
"This is a AirDelivery instance, so its createTransport() override runs.",
});
const transport = new Plane();
record({
phase: "create",
message:
"The override constructs a Plane object. It implements Transport.",
});
return transport;
}
}Concrete products · TypeScript
export class Truck implements Transport {
deliver(shipment: Shipment): DeliveryReceipt {
return Object.freeze({
vehicle: "Truck",
destination: shipment.destination,
message: `Truck drives to ${shipment.destination}.`,
});
}
}
export class Ship implements Transport {
deliver(shipment: Shipment): DeliveryReceipt {
return Object.freeze({
vehicle: "Ship",
destination: shipment.destination,
message: `Ship sails to ${shipment.destination}.`,
});
}
}
export class Plane implements Transport {
deliver(shipment: Shipment): DeliveryReceipt {
return Object.freeze({
vehicle: "Plane",
destination: shipment.destination,
message: `Plane flies to ${shipment.destination}.`,
});
}
}Application setup · TypeScript
export function chooseDelivery(kind: DeliveryKind): DeliveryService {
if (kind === "road") return new RoadDelivery();
if (kind === "sea") return new SeaDelivery();
return new AirDelivery();
}
export function sendPackage(
service: DeliveryService,
shipment: Shipment,
): DeliveryReceipt {
return service.dispatch(shipment);
}TypeScript excerpts come from the running model. Kotlin is an equivalent example; compilation is unverified.
Adding air delivery
- Implement Transport with a new Plane class.
- Add a DeliveryService subclass that returns a Plane from createTransport().
- Choose the new creator in application setup.
DeliveryService.dispatch() and existing creators remain unchanged. Setup still knows which creator to select. Our example creates a fresh vehicle object per dispatch; Factory Method does not inherently require a new instance each time.
The demo trims and validates a nonblank destination of up to 60 characters before creating the vehicle. It returns an immutable receipt from a local simulation. A real delivery integration also needs route eligibility, booking, failure and tracking policies.
Choose the extension mechanism deliberately.
Useful when
A framework owns common work and subclasses need to decide which compatible object participates in it.
Cost and alternatives
More product types can mean more creator subclasses. A factory function, a short switch, or an injected dependency is often enough without a subclass extension point.
- A function named create() is not automatically this pattern. The key here is an overridable creation method used by a creator's shared workflow.
- Do not copy dispatch() into each subclass. That loses the shared workflow.
- Builder assembles a product through steps; Factory Method varies which product the workflow gets.
- Strategy supplies interchangeable behavior through composition; this example varies creation through inheritance.
- Changing the product interface can still require changes to its users and implementations.