Abstract Factory
Occasional. Turns up in specific problems. Worth recognising and being able to sketch, not worth drilling.
Start with the problem
corpus/vending-machine's MachineFactory assembles one kind of machine from five collaborators: a rack, a coin float, a tariff, a change-maker, and a transition table.
public static VendingMachineApi standard() {
return new VendingMachine(
new SlotRack(standardSlots()),
new CoinPurse(standardFloat()),
new ListPricing(),
new GreedyChangeMaker(),
new TransitionTable(Transitions.standard()));
}
One method, one machine, built from parts that belong together. That is fine, and it stays fine as long as there is one product line.
Watch where it goes
Suppose the business now sells two lines of machine, not one. A snack machine needs a wider rack and a change-maker tuned for coins. A drink machine needs a refrigerated rack and a change-maker tuned for a bill acceptor. Each part on one line has to match the other parts on that same line: a snack rack paired with a drink machine's change-maker is a bug, not a variant.
A second static method on MachineFactory, snack() and drink(), each still building all five parts by hand, gets you most of the way there. What it does not get you is a guarantee. Both methods return a finished VendingMachineApi, with no seam in between where a mismatch could be caught. Nothing stops a caller from taking the rack out of snack() and the change-maker out of drink() and wiring them together anyway.
The move
Give the parts their own factory interface, one creation method per part, and one implementation per product line.
interface MachineHardwareFactory {
SlotRack rack();
PricingPolicy pricing();
ChangeMaker changer();
}
final class SnackHardwareFactory implements MachineHardwareFactory {
@Override public SlotRack rack() { return new SlotRack(MachineFactory.standardSlots()); }
@Override public PricingPolicy pricing() { return new ListPricing(); }
@Override public ChangeMaker changer() { return new CoinOnlyChangeMaker(); }
}
final class DrinkHardwareFactory implements MachineHardwareFactory {
@Override public SlotRack rack() { return new SlotRack(RefrigeratedSlots.standard()); }
@Override public PricingPolicy pricing() { return new ListPricing(); }
@Override public ChangeMaker changer() { return new BillAcceptorChangeMaker(); }
}
VendingMachine's constructor now takes one MachineHardwareFactory, not a rack and a change-maker separately, and asks it for all three parts. Whoever builds the machine picks a product line once, by choosing which factory to hand over. Nothing downstream can mix a snack rack with a drink change-maker, because both came out of the one factory object the machine was given.
This is Abstract Factory: an interface with one method per member of a family, and more than one concrete implementation, each producing a matched set. The guarantee is that a caller who picks one factory gets a consistent family, and cannot assemble a broken combination even by accident.
What modern Java changes here
Most Java teams solve "pick a matched set of collaborators" through a dependency-injection container rather than a hand-written factory interface. A Spring profile or a Guice module binds a whole family under one name, and the caller selects a family by activating a profile, not by choosing which factory object to construct. The abstract factory interface above is what that container is doing under the hood, made visible, and it is worth being able to sketch precisely because the container hides it.
When the set of product lines is genuinely fixed and known up front, sealing MachineHardwareFactory's implementations buys the same exhaustiveness check the corpus's sealed-hierarchy lesson describes for data types. Here it applies to a family of factories instead of a family of values.
When naming it is wrong
The single most common misuse is calling any static method with Factory in its name an Abstract Factory. The pattern needs two axes: more than one kind of part being built, and more than one consistent family of them. A class with one static method that returns one kind of object is a factory method, not an abstract factory, no matter what the file is named.
If a second product line cannot be named yet, building the family interface above is speculative. Wait for the second line to be a real requirement, the same way factory-method.md's threshold asks for a second implementation before extracting a seam at all.
Where this lives in the app
The anchor given for this page is B2, corpus/vending-machine. Verified: MachineFactory builds one product line, the standard machine, with one collaborator swappable for a promotion. That is Factory Method, and it is what factory-method.md covers. Nothing in this app builds two parallel families through a shared factory interface, so there is no real Abstract Factory example here. B2 is the closest available shape to reason from, and the difference above is the reason it is not one.