81. Factory Method Pattern
Factory methods decouple object creation from clients — they can return subtype instances, cache instances, or vary the concrete type — overcoming the limitations of constructors (List.of(), Optional.of() are classic examples).
Explanation
A factory method is a static (or instance) method that creates and returns an object, instead of calling new directly. Advantages over constructors: they have a meaningful name (valueOf, of, from, getInstance, create); they can return a subtype (hiding the concrete class); they can return cached instances (like Integer.valueOf(127)); they can fail more expressibly.
Static factory method pattern (Effective Java Item 1): use this when a class might want to cache instances, when the return type might change, or when a more descriptive name helps readability. Downsides: the class cannot be subclassed if all constructors are private, and they're harder to discover than constructors (not in Javadoc's constructor summary).
The Factory Method design pattern (GoF): defines an interface for creating an object but lets subclasses decide which class to instantiate. More heavyweight — use static factory methods first; reach for GoF Factory Method only when you need polymorphic creation.
Example
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class FactoryMethodPattern {
// Static factory methods: named, can return subtypes, can cache
interface Shape {
double area();
String name();
// Static factory on the interface (Java 8+)
static Shape circle(double radius) { return new Circle(radius); }
static Shape rectangle(double w, double h) { return new Rect(w, h); }
}
private record Circle(double radius) implements Shape {
@Override public double area() { return Math.PI * radius * radius; }
@Override public String name() { return "Circle(r=" + radius + ")"; }
}
private record Rect(double w, double h) implements Shape {
@Override public double area() { return w * h; }
@Override public String name() { return "Rect(" + w + "x" + h + ")"; }
}
// Caching factory (like Integer.valueOf for -128..127)
static class ConnectionPool {
private static final Map<String, ConnectionPool> cache = new HashMap<>();
private final String url;
private ConnectionPool(String url) { this.url = url; }
public static ConnectionPool forUrl(String url) {
return cache.computeIfAbsent(url, ConnectionPool::new);
}
@Override public String toString() { return "Pool[" + url + "]"; }
}
public static void main(String[] args) {
// Shape factory — clients don't know the concrete class
Shape c = Shape.circle(5);
Shape r = Shape.rectangle(3, 4);
System.out.printf("%s area = %.2f%n", c.name(), c.area());
System.out.printf("%s area = %.2f%n", r.name(), r.area());
// Standard library factory methods
List<String> list = List.of("a", "b", "c");
Optional<String> opt = Optional.of("value");
Optional<String> empty = Optional.empty();
Map<String, Integer> map = Map.of("x", 1, "y", 2);
System.out.println("List: " + list);
System.out.println("Optional: " + opt);
System.out.println("Empty: " + empty);
// Caching factory: same URL -> same instance
var pool1 = ConnectionPool.forUrl("jdbc:postgresql://localhost/db");
var pool2 = ConnectionPool.forUrl("jdbc:postgresql://localhost/db");
System.out.println(pool1 == pool2); // true — cached
}
}Sources
- https://www.pearson.com/en-us/subject-catalog/p/effective-java/P200000000138 — Effective Java, Item 1
- https://refactoring.guru/design-patterns/factory-method — Factory method reference