Scattering new ConcreteThing() across a codebase glues every caller to a specific
class. The day you add a variant, you grep for every construction site and edit each one.
Factory Method centralizes that decision behind a single seam so callers ask for a
capability and stay ignorant of which concrete class satisfies it.
Start with the smell: construction logic tangled into the code that uses the object.
// Scattered construction — every caller decides the concrete type.
Shape s;
if (kind.equals("circle")) s = new Circle(radius);
else if (kind.equals("square")) s = new Square(side);
else throw new IllegalArgumentException(kind);
s.draw();
That if ladder is copy-pasted wherever a shape is built. Pull the choice into one
factory and callers depend only on the Shape interface:
interface Shape { void draw(); }
class ShapeFactory {
Shape create(String kind) {
return switch (kind) {
case "circle" -> new Circle();
case "square" -> new Square();
default -> throw new IllegalArgumentException("unknown: " + kind);
};
}
}
// Caller: no 'new', no knowledge of concrete classes.
Shape s = factory.create(kind);
s.draw();
The payoff arrives on day two. Adding a Triangle touches exactly one place — the
factory — instead of every construction site. It also gives you a natural home for
construction concerns like pooling, caching, or reading the type from config. The cost is
one extra indirection, so reach for it when the set of concrete types is genuinely open or
the choice logic is duplicated; skip it when you build one fixed type in one place.