LLD Dojo

Factory Method

Core. Expect to meet this one, and expect to be asked for it by name.

Start with the problem

A vending machine needs five collaborators before it can sell anything. It needs a rack of slots, a coin float, a pricing policy, a change maker, and a table of which transitions are legal right now. The obvious way to get one running is to build all five wherever the machine is needed.

VendingMachine machine = new VendingMachine(
        new SlotRack(List.of(new Slot("A1", "Chips", 65, 2))),
        new CoinPurse(Map.of(Coin.QUARTER, 1)),
        new ListPricing(),
        new GreedyChangeMaker(),
        new TransitionTable(Transitions.standard()));

For a single call site, this is fine. It states exactly what the machine is made of, and a reviewer can check every argument against the requirement in front of them.

Watch where it goes

A demo needs a second machine, stocked differently for a trade show. A test needs a third, with an empty float so it can check what happens when change runs out. Each one repeats the same five constructor calls, changing only a value or two inside them.

Now the requirement changes: the coin float gets a new denomination, a dollar coin. Every call site that built a float has to be found and edited, because the float was written out by hand each time. A promotional tariff arrives next, and it needs its own machine to test against, built the same way as the standard one except for ListPricing.

The real cost is not the five arguments themselves. "What a standard machine is made of" is a fact about the business. That fact is currently copied into every place that needs one. A second requirement change proves it: the pricing policy's constructor gains a discount argument. Three call sites now need the identical edit, or one of them quietly falls out of sync with the rest.

The move

Pull "how to build one" into a class of its own, separate from the class that runs a sale.

public final class MachineFactory {

    public static VendingMachineApi standard() {
        return new VendingMachine(
                new SlotRack(standardSlots()),
                new CoinPurse(standardFloat()),
                new ListPricing(),
                new GreedyChangeMaker(),
                new TransitionTable(Transitions.standard()));
    }

    public static List<Slot> standardSlots() {
        return List.of(
                new Slot("A1", "Chips", 65, 2),
                new Slot("A2", "Chocolate", 100, 1));
    }

    public static Map<Coin, Integer> standardFloat() {
        return Map.of(Coin.NICKEL, 1, Coin.QUARTER, 1);
    }

    private MachineFactory() {}
}

VendingMachine itself keeps a constructor that takes all five collaborators and nothing else, so it never learns what a standard float looks like. corpus/vending-machine's MachineFactory is exactly this shape. A new coin denomination now lands only in standardFloat, and the class that handles money and stock during a sale is never touched. A promotional tariff becomes a second method, MachineFactory.promotional(), reusing standardSlots() and standardFloat() rather than copying them.

What modern Java changes here

The name "Factory Method" in the original catalogue describes something more specific than a static factory. An abstract creator class declares a method that returns a product. Each concrete subclass overrides that method to return a different product. MachineFactory.standard() is not that. It is a static method on a final class, with no subclass in sight. That is by far the more common shape in Java code written today. Call it a static factory, the name Effective Java gives it, rather than reaching for the older term out of habit.

The subclass-hook version still earns its place once the decision about which concrete type to build has to vary by a whole family of caller. A static method taking an argument cannot express that. Nothing in this corpus needs it. promotional() and standard() are two methods on one factory, because the difference between them is a pricing policy, not a hierarchy of types.

A record can stand in for the five constructor arguments once there are enough of them to want a name. That alone is not the reason to reach for a factory class, though. Assembly and operation change for different causes, and keeping them apart means a change to one never risks the other.

When naming it is wrong

A factory with one caller and one product is a static method standing where a constructor call would have done the same job. If MachineFactory.standard() were called from exactly one place, inlining it back to a direct new VendingMachine(...) would lose nothing, and the extra file would cost a reader an extra jump for no reason.

The threshold: reach for a factory once assembly has two or more collaborators worth naming individually. The other trigger is a second recipe for the same product, already in view: a promotion, a test double, a demo build. One product, one caller, nothing else in sight, is ceremony. The Standard's D3 dimension scores that below the plain constructor call it replaced, tagged over-engineered (premature interface).

Where this lives in the app

Syllabus item B2 builds this out against corpus/vending-machine. It measures three real requirement changes against MachineFactory: a coin denomination, a promotional tariff, and a maintenance mode. None of the three ever reaches VendingMachine itself.

All reference pages