A logistics startup ships its first product with one method that plans a delivery: pack the cargo, dispatch a truck, hand back a receipt. Clean. Then the company wins a port contract and has to move freight by sea. Someone opens planDelivery(), adds if (mode == "sea") new Ship() next to the truck, and ships it. A year later there’s air freight, cold-chain, and last-mile bikes — and planDelivery() is a switch with five arms wrapped around three lines of logic that were the same the whole time.
The workflow never changed. Loading cargo and dispatching it and writing a receipt is identical whether the thing that moves is a truck or a ship. The only thing that varies is the one line that decides which vehicle to build — and that line is stuck in the middle of the code everyone is afraid to touch. Every new transport is a diff to the one method that must never break.
The Gang of Four’s fix is to take that single decision — which class do I instantiate? — and hand it to a subclass.
The intuition: the recipe is fixed, the ingredient is chosen at the last step
Think of a delivery dispatcher who follows the same checklist for every shipment — weigh it, label it, load it, log it. Nowhere on that checklist does it say truck or ship. There’s just one line: “load onto the assigned vehicle.” Which vehicle shows up at the dock is decided by whoever is running that route — the road team sends a truck, the sea team sends a ship. The dispatcher’s checklist never changes when a new route opens; a new team just brings a new vehicle.
The checklist is the Creator’s shared method. “Load onto the assigned vehicle” is the factory method. The truck and the ship are Products. The road team and the sea team are concrete Creators — each one answers the single question the checklist left open.
Why it exists: the smell it removes
Here’s the delivery method before the pattern — the shared steps we actually care about are trapped behind a switch that must be reopened for every new transport.
Two things are wrong, and they’re the same thing. First, Logistics knows the names of every concrete transport — it can’t be reused or unit-tested without dragging Truck and Ship along. Second, the decision and the workflow change for different reasons but live in one method: adding air freight (a creation concern) forces you to edit the delivery steps (a workflow concern), and one slip breaks routes you never meant to touch.
Factory Method separates the two. The workflow moves into a base class and freezes. The new moves into subclasses, one per product. Watch it happen.
See it live
Below, the client code and the Creator’s planDelivery() are frozen — they never change as you click. Switch the Creator subclass and only one thing moves: which concrete Product the factory method returns, and therefore how the cargo travels. Adding a third transport (air) is just a third button — nothing on the left had to change to make room for it.
Logistics logistics = pickLogistics(config); logistics.planDelivery(cargo);
product = createTransport() // factory method → Truckproduct.drive(cargo) // deliver via the Product interfacereturn receiptTruck.drive(cargo)That’s the whole promise made tangible. The caller says logistics.planDelivery(cargo) and gets truck behavior or ship behavior depending only on which subclass it’s holding — with zero conditional logic in the method doing the work.
The structure
Four roles, and one arrow that carries the pattern: the Creator’s workflow points at the Product interface, never at a concrete product. The concrete Creators sit on the bottom row, free to multiply.
The uses arrow from Logistics to Transport is the one that matters. Because it points at the interface, the concrete-Product column on the right can grow without end, and the planDelivery() logic above never learns a single one of their names.
The code
Three parts, in the order you’d write them: the Product contract, the abstract Creator that owns the shared logic and defers the new, and the concrete Creators that override only the factory method.
Read it top to bottom and the discipline is visible. Transport is the entire vocabulary the Creator is allowed to know. Logistics.planDelivery() is written once and calls createTransport() — an abstract method it can’t itself answer. Each concrete Creator supplies exactly one line: which class to build. There is no switch, because there is nothing to branch on — the base class doesn’t know which subclass it is, and that ignorance is the feature.
Now the payoff at the call site. The only place that names a concrete Creator is the wiring line; everything downstream is blind to the transport.
Note where the switch went: it didn’t vanish, it shrank and moved to the edge. Instead of a conditional in the middle of the workflow that runs on every delivery, there’s a one-time selection at the boundary that picks a subclass. The valuable logic in the center is now conditional-free.
When (and when not) to reach for it
In the wild
You’ve called Factory Methods for years without naming them.
The tradeoffs
Factory Method buys extensibility with a hierarchy. Name the cost honestly.
- Open/Closed: a new product is a new subclass, not an edit to the workflow
- The creator depends only on the Product interface — decoupled and testable
- No conditional in the shared logic; the choice moves to the boundary
- Frameworks can ship the workflow and let you fill in the concrete type
- A parallel class hierarchy: one Creator subclass per product
- More classes and indirection than a plain switch for a single product
- You must already have (or want) a Creator subclass axis to hang it on
- Overkill when a simple factory or a supplier/lambda would do
Factory Method vs Abstract Factory vs Simple Factory
This trio is where interviews go to separate the memorizers from the understanders. They rhyme; their intent is different.
The one-line tell: Factory Method makes one product via a subclass; Abstract Factory makes a whole family via an object you pass around. If you’re overriding a method to change one product’s class, it’s Factory Method. If you’re swapping an object to change a coordinated set of products, it’s Abstract Factory.
Interview corner
A cross-platform dialog framework renders a window, then news up a concrete button by platform — with the platform switch buried inside the shared render() loop. Same smell as the logistics method, different domain.
Check yourself
References
Books
- Gamma, Helm, Johnson, Vlissides — Design Patterns: Elements of Reusable Object-Oriented Software, the Factory Method chapter (and its comparison to Abstract Factory).
Official documentation
- Java SE —
java.util.Iteratorandjava.util.Collection.iterator(). - Java SE —
java.util.Calendar.getInstance().
Discussion
Loading the conversation…
Discussion
Loading the conversation…