Patterns you will actually be asked for · chapter 20 of 33
Factory: construction kept away from use
Chapter 3.2 · Part 3, Patterns you will actually be asked for · about 30 minutes
What you need before this chapter: Part 1 in full, especially constructors (1.2) and collections (1.6). Part 2 in full, especially single responsibility (2.4). Chapter 3.1, Strategy, since the class you build here will often be handed a strategy rather than choosing one for itself.
When you finish this chapter you will be able to:
- Recognise repeated, multi-argument construction at several call sites as a sign that "how to build one" needs a home of its own, separate from the class being built
- Write a static factory class that assembles an object's collaborators, and a second method on it for a variant that reuses shared parts
- Explain the difference between a static factory method and the Gang of Four's Factory Method, and say which one Java code reaches for by default
- Say when a factory buys nothing over a direct constructor call, and defend that call under questioning
1. The situation
A vending machine needs two things before it can do anything. A rack of slots holding products, and a float of coins it can hand back as change.
Both are real collaborators with their own state. So build both, right where you need the machine.
VendingMachine production = new VendingMachine(
new SlotRack(List.of(new Slot("A1", "Chips", 65, 2), new Slot("A2", "Chocolate", 100, 1))),
new CoinFloat(Map.of(5, 1, 25, 1)));
2. Naive code that is fine
javac Step1.java
java Step1
2 slots, float 30 cents
For a single call site this is exactly right. It states precisely what the machine is made of, and you can check every value against the requirement sitting in front of you.
Nothing here needs hiding yet.
3. A new requirement
You need a second machine for a trade-show demo. Same stock, bigger float, so it can hand back change all day without running dry.
Then a third, inside a test, with an empty float — you want to see what happens when a customer's change cannot be made.
VendingMachine demo = new VendingMachine(
new SlotRack(List.of(new Slot("A1", "Chips", 65, 2), new Slot("A2", "Chocolate", 100, 1))),
new CoinFloat(Map.of(5, 5, 25, 5)));
VendingMachine forChangeTest = new VendingMachine(
new SlotRack(List.of(new Slot("A1", "Chips", 65, 2), new Slot("A2", "Chocolate", 100, 1))),
new CoinFloat(Map.of()));
javac Step2.java
java Step2
production: 2 slots, float 30 cents
demo: 2 slots, float 150 cents
for change test: 2 slots, float 0 cents
All three machines behave correctly. Now look at what got typed three times, identically. The rack contents. Keep your eye on that.
4. Watch where it goes, and the real cost
A new coin joins circulation. A one-dollar coin, worth 100 cents.
Whoever adds support for it has to find every place a CoinFloat gets built. Miss one and two of your three machines cannot give a dollar back as change. Three call sites, one identical edit, and nothing connecting them except somebody remembering to do it three times.
The length of any single constructor call is not the problem. Five arguments on one line reads fine.
The problem is what "a standard machine's rack" is. That is a fact about the business, not a fact about any one call site. Right now it gets copied into every place that needs a machine.
Make it concrete. Gum comes back into stock, so the standard rack changes. That edit has to land in the production build, the demo build, and the test build, identically, or the three quietly drift apart.
And a test that differs from production because somebody forgot one Slot is far worse than a test that differs on purpose.
5. The move
Separate two facts that have been tangled together. What a standard machine is made of, and how a machine runs a sale.
The second never has to change when the first does. So pull the assembly into a class of its own.
final class MachineFactory {
static VendingMachine standard() {
return new VendingMachine(new SlotRack(standardSlots()), new CoinFloat(standardFloat()));
}
static VendingMachine promotional() {
return new VendingMachine(new SlotRack(standardSlots()), new CoinFloat(Map.of(5, 20, 25, 20)));
}
static VendingMachine forChangeTest() {
return new VendingMachine(new SlotRack(standardSlots()), new CoinFloat(Map.of()));
}
static List<Slot> standardSlots() {
return List.of(new Slot("A1", "Chips", 65, 2), new Slot("A2", "Chocolate", 100, 1));
}
static Map<Integer, Integer> standardFloat() {
return Map.of(5, 1, 25, 1);
}
private MachineFactory() {}
}
VendingMachine keeps its two-argument constructor and nothing else. It never learns what a standard rack looks like, or that a promotional variant exists.
javac Step3.java
java Step3
production: 2 slots, float 30 cents
demo: 2 slots, float 600 cents
for change test: 2 slots, float 0 cents
standardSlots() is written once and called by every variant. The rack cannot drift between your production build and your test build, because there is only one of it.
Notice the shape of the class too. MachineFactory is final, its constructor is private, and every method on it is static. Nothing about it is meant to be instantiated. It is a namespace for build recipes.
That is Factory Method in the shape Java reaches for by default: a static method whose whole job is knowing how to build something, kept apart from the class it builds.
6. What modern Java changes here
Be careful with the name here, because it will come up in interviews.
"Factory Method" in the 1994 catalogue means something more specific than MachineFactory.standard(). In that book an abstract creator class declares a method returning some product, and each concrete subclass overrides it to return a different product. Which product you get depends on which subclass you instantiated.
MachineFactory is not that. It is a static method on a class with no subclasses at all.
And that shape is, by a wide margin, the more common one in Java written today. Effective Java calls it a static factory method. Use that name instead of reaching for "Factory Method" out of habit, because they are two different tools.
When does the subclass-hook version earn its place? Once the decision about which whole family of type to build has to vary by caller, in a way a single method taking an argument cannot express.
Nothing in this chapter needs that. standard() and promotional() are two methods on one class, because those two machines differ by which collaborators they are handed. No new classes exist.
7. When naming it is wrong
A factory with exactly one caller and one product is a static method standing exactly where a plain constructor call would have done the same job, with one less file to open.
If MachineFactory.standard() had one caller anywhere in the codebase, inlining it back to `new VendingMachine(...)` would lose you nothing.
Here is the threshold. Reach for a factory once assembly involves two or more collaborators worth naming individually, or once a second recipe for the same product exists — a promotion, a test double, a demo build.
One product, one caller, nothing else on the horizon? That is ceremony, and this app's grading standard scores it below the plain constructor call it replaced, tagged `over-engineered (premature interface)`.
Your turn
Add support for a one-dollar coin (100 cents) to the standard float, one of them. Change nothing about VendingMachine itself.
The answer.
static Map<Integer, Integer> standardFloat() {
return Map.of(5, 1, 25, 1, 100, 1);
}
javac Step4.java
java Step4
production: 2 slots, float 130 cents
The slot count does not move, because the dollar coin is a fact about the float and not the rack. One line changed, in one file.
Going deeper
There is a second reason to prefer a static factory method over a public constructor, and it has nothing to do with hiding assembly.
A static method can decide not to build a new object, and hand back one it already has.
A constructor can never do that. new always produces a fresh object, every single time. A static method is free to look something up, cache it, or return a subtype the caller never names — all behind a signature that looks like ordinary construction.
The standard library leans on this constantly. Integer.valueOf(int) is a static factory method, and the JDK uses it to cache every boxed Integer from -128 to 127. Those are the values programs box most, and there is no reason to allocate a new object for 5 every time one appears.
Integer a = Integer.valueOf(127);
Integer b = Integer.valueOf(127);
System.out.println("127 == 127 (valueOf): " + (a == b));
Integer c = Integer.valueOf(128);
Integer d = Integer.valueOf(128);
System.out.println("128 == 128 (valueOf): " + (c == d));
javac Step5.java
java Step5
127 == 127 (valueOf): true
128 == 128 (valueOf): false
127 == 127 (new): false
127 == 127 (autoboxed literal): true
Work through those four lines carefully, because one of them has bitten almost everybody.
== on boxed types compares object identity — the same identity comparison from chapter 1.3 — and never the numeric value.
Two calls to Integer.valueOf(127) return the very same cached object, so == reports true.
Go one higher, to 128, and you fall outside the cached range. valueOf builds two separate objects and == reports false. Correct for what == actually checks, and still a trap for anyone expecting it to compare numbers.
Writing new Integer(127) always allocates, whatever the value. That is one reason the constructor is deprecated for removal, and the compiler tells you so:
Step5.java:11: warning: [removal] Integer(int) in Integer has been deprecated and marked for removal
Now the last line, which is the one most people have relied on without knowing it.
An ordinary autoboxed literal, Integer g = 127;, compiles to Integer.valueOf(127). Not to `new Integer(127)`. So it gets the cached object too.
That is exactly why comparing boxed Integer values with == passes every test written with small numbers, then fails silently the day a value crosses 127. A static factory's caching made that bug possible, and only .equals() is safe against it.
Three reasons a static factory beats a public constructor sit side by side in this one example. It can have a name that says something a constructor's fixed shape cannot — standard() against a bare new VendingMachine(...). It can return a cached instance. And it can return a subtype the caller never has to name, though Integer does not show you that one.
Why this matters in an interview
Hand an interviewer a system with several collaborators and they are watching for one moment.
Do you notice that "how this gets built" is a fact worth writing down once, before somebody asks you for a second variant for a test or a demo?
Naming the factory matters far less than spotting the repetition. What lands hardest is saying precisely which requirement change would force you to edit three places instead of one.
Next: chapter 3.3, Builder: too many constructor arguments. A factory solves who assembles the object. The next problem is what happens when most of what it is built from is optional, and one part of it can be added more than once.
← 3.1 Strategy: behaviour you can swap · All chapters · 3.3 Builder: too many constructor arguments →