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 useful: a rack of slots holding products, and a float of coins it can give back as change. Both are real collaborators with their own state, so the obvious way to get a machine running is to build both, right where the machine is needed.
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 anyone reading it can check every value against the requirement in front of them. There is no reason to hide this behind anything else yet.
3. A new requirement
A second machine is needed for a trade-show demo, stocked the same way but with a bigger float so it can hand back change all day without running dry. A third is needed inside a test, with an empty float, to check 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. The rack contents are typed out three times, identically, and that repetition is the detail to watch.
4. Watch where it goes, and the real cost
A new coin denomination joins circulation: a one-dollar coin, worth 100 cents. Whoever adds support for it has to find every place a CoinFloat gets built and add the new coin, or leave two of the three machines unable to give it out as change. Three call sites now need the identical edit, and nothing connects them except that a person remembers to make the same change three times.
The length of any one constructor call is not the problem — five arguments in one line reads fine. The problem is what "a standard machine's rack" actually is. That is a fact about the business, not a fact about any one call site, and right now that fact is copied into every place that needs a machine. A second business fact makes this concrete: the standard rack itself changes, say gum comes back into stock. That edit has to land in the production build, the demo build, and the test build, in the same way, or the three quietly drift apart. A test that is subtly different from production because someone forgot one Slot is worse than a test that is honestly different on purpose.
5. The move
Separate "what a standard machine is made of" from "how a machine runs a sale." The second class never needs to change when the first one does, so pull the assembly logic 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 itself keeps its two-argument constructor and nothing else. It never learns what a standard rack looks like, or that a promotional variant exists at all.
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 that needs it, so the rack cannot drift between the production build and the test build. MachineFactory is final, and its constructor is private, because nothing about it is meant to be instantiated. Every method on it is static. It is a namespace for build recipes, not an object with state of its own.
This is Factory Method in the shape Java code reaches for by default: a static method whose whole job is to know how to build something, kept apart from the class it builds.
6. What modern Java changes here
The name "Factory Method" in the original 1994 catalogue describes something more specific than MachineFactory.standard(). In that book, an abstract creator class declares a method that returns some product, and each concrete subclass overrides it to return a different product, chosen by which subclass you instantiated. MachineFactory is not that. It is a static method on a class with no subclasses at all, and that is by a wide margin the more common shape in Java written today. Effective Java calls this a static factory method, and the name is worth using instead of reaching for "Factory Method" out of habit, since they are not quite the same tool.
The subclass-hook version earns 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 factory class, because the two machines differ by which collaborators they are handed, not by which classes exist.
7. When naming it is wrong
A factory with exactly one caller and one product is a static method standing where a plain constructor call would have done the same job with one less file to open. If MachineFactory.standard() had exactly one caller anywhere in the codebase, inlining it back to new VendingMachine(...) would lose nothing.
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 somewhere — a promotion, a test double, a demo build. One product, one caller, and nothing else on the horizon is ceremony, and this app's grading standard scores that below the plain constructor call it replaced, under the tag 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 is unaffected, because the dollar coin is a fact about the float, not the rack, and this one-line edit to standardFloat is the only file that changed.
Going deeper
There is a second, independent reason to prefer a static factory method over a public constructor. It has nothing to do with hiding assembly: a static method can decide not to build a new object at all, 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 method signature that looks like ordinary construction.
The standard library leans on this constantly. Integer.valueOf(int) is a static factory method. The JDK uses it to cache every boxed Integer value from -128 to 127, because those are by far the most common values a program boxes, and there is no reason to allocate a new object for 5 every time one shows up.
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
== on boxed types compares object identity, the same identity comparison chapter 1.3 introduced, not the numeric value. Two calls to Integer.valueOf(127) return the exact same cached object, so == reports true. One value higher, at 128, falls outside the cached range. valueOf then builds two separate objects, and == correctly reports false: correct for what == actually checks, and still a trap for anyone who expected it to compare numbers. Writing new Integer(127) explicitly always allocates a fresh object regardless of the value. That is one of the reasons this constructor is deprecated for removal, and the compiler says exactly that:
Step5.java:11: warning: [removal] Integer(int) in Integer has been deprecated and marked for removal
The last line is the one most people have actually relied on without knowing it. An ordinary autoboxed literal, Integer g = 127;, compiles to a call to Integer.valueOf(127), not to `new Integer(127), so it gets the cached object too. This is precisely why comparing boxed Integer` values with == instead of .equals() passes every test written with small numbers, then fails silently the day a value crosses 127. It is a bug that a static factory's caching made possible, and only .equals() is safe against it.
Three reasons a static factory method beats a public constructor sit right next to each other 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 instead of building one. And, though Integer does not show this one, it can return an instance of a subtype the caller never has to name.
Why this matters in an interview
An interviewer handing you a system with several collaborators is watching for one moment in particular. Do you notice that "how this gets built" is a fact worth writing down once, before you are asked to build a second variant for a test or a demo? Naming the factory matters far less than noticing the repetition, and being able to say, 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 →