Factory Pattern: Creating Objects Without if-else Chains
Where a factory helps and where it is overkill: a simple factory, a registry-based factory in Java where adding a new shape needed no change to the callers, and when plain constructors are better.
The factory pattern puts object creation in one method so callers depend on an interface rather than concrete classes. A simple factory uses a switch; a registry factory maps names to creator functions. Interviewers expect the distinction: a factory method lets subclasses choose a single product, while an abstract factory creates families of related products that must work together.
A factory is a method that creates objects so callers depend on an interface, not on concrete classes. A registry-based factory in Java let us add a new shape with one line and no change to calling code. The pattern pays off when creation logic varies or the type is chosen at runtime, and is overkill for a single class.
- A factory centralises creation so callers ask for an object by name or parameters and receive an interface, never naming concrete classes.
- A simple factory built on a switch statement must still be edited for every new type, so it is a single place to change rather than open for extension.
- In a Java registry-based factory, adding a square shape took one line, REG.put("square", ...), with no change to the factory method or its callers.
- Factory Method lets subclasses override a creation method to choose one product type; Abstract Factory creates families of related objects, such as UI widgets per look and feel.
- JDK static factory methods include List.of, Optional.of, Integer.valueOf and LocalDate.of, and Java's ServiceLoader is a built-in registry-style mechanism.
01The problem
If code everywhere says new Circle(...) or new Rect(...), adding another shape, or choosing the shape from user input or a config file, means editing all of those places. When the type to create is only known at runtime, you end up writing the same if kind == ... else if ... chain in several spots.
A factory puts the creation decision in one place. Callers ask for something by name or by parameters and receive an object that satisfies an interface. They never mention concrete classes. That is the main point: the construction details can change without the callers changing.
02Simple factory
interface Shape { double area(); }
record Circle(double r) implements Shape { public double area() { return Math.PI * r * r; } }
record Rect(double w, double h) implements Shape { public double area() { return w * h; } }
static Shape make(String kind, double... a) {
return switch (kind) {
case "circle" -> new Circle(a[0]);
case "rect" -> new Rect(a[0], a[1]);
default -> throw new IllegalArgumentException("unknown shape: " + kind);
};
}This is a simple factory, not a formal pattern from the original design patterns book, but the most common form in practice. The caller writes make("circle", 1). The switch is still a place that must change for each new type, so it is not open for extension, though it is a single place instead of many.
03Registry-based factory
static final Map<String, Function<double[], Shape>> REG = new HashMap<>();
static {
REG.put("circle", a -> new Circle(a[0]));
REG.put("rect", a -> new Rect(a[0], a[1]));
}
static Shape make(String kind, double... a) {
Function<double[], Shape> f = REG.get(kind);
if (f == null) throw new IllegalArgumentException("unknown shape: " + kind);
return f.apply(a);
}Running make("circle", 1).area() gave 3.14 and make("rect", 2, 3).area() gave 6.00 when formatted with %.2f (unformatted: 3.141592653589793 and 6.0), and make("hex", 1) threw unknown shape: hex. Then I added a new type with one line at run time, REG.put("square", a -> new Rect(a[0], a[0])), and make("square", 4).area() returned 16.0 without touching make or any caller. This is the open/closed principle at work: open to new shapes, closed to modification of the factory. Plugins and frameworks work this way. Java's ServiceLoader is a built-in version.
04Factory method and abstract factory
Factory Method: a base class declares a creation method that subclasses override. A Document class has createPage(), and PdfDocument returns a PDF page while HtmlDocument returns an HTML page. The base class code uses whatever page it is handed, and the subclass chooses the type.
Abstract Factory: an interface to create a family of related objects that must work together. A UI toolkit might offer createButton() and createCheckbox(), with one factory per look and feel, so a Windows factory never mixes with a macOS button. It is used less often, and mostly in toolkits and when swapping entire families such as database drivers.
Static factory methods (List.of(...), Integer.valueOf(...), LocalDate.of(...)) are a related, everyday form. They have names, can return cached or subclass instances, and need not create a new object on each call.
05When not to use one
If there is one concrete class and no realistic prospect of another, new is clearer. Factories add indirection, and the reader has to jump through a method to see what is created. Signs that you need one: creation requires choosing between types based on runtime data, setup steps that should not be repeated at each call site (validation, defaults, wiring dependencies), a need to cache or pool instances, or tests that need to substitute a fake.
With dependency injection frameworks, much of the factory work is done by the container. You still write explicit factories when an object needs runtime parameters that the container cannot know.
06Common interview questions
Q: What problem does the factory pattern solve?
A: It decouples callers from concrete classes and centralises creation logic.
Q: Factory method versus abstract factory?
A: Factory method lets subclasses decide which single product to create. Abstract factory creates families of related products.
Q: How do you add a type without modifying the factory?
A: Use a registry (map from key to creator function) or a plugin mechanism such as ServiceLoader.
Q: Example of a static factory in the JDK?
A: List.of, Optional.of, Integer.valueOf, LocalDate.of.
Q: When is a factory overkill?
A: With a single concrete type and no varying creation logic.
Q: Which principle does the registry version support?
A: Open/closed: open for extension, closed for modification.