← Back to PatternPip

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

  1. Implement Transport with a new Plane class.
  2. Add a DeliveryService subclass that returns a Plane from createTransport().
  3. 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.