Design Patterns in Java
Design patterns are named solutions to recurring design problems. Learn them as vocabulary, not as rules to force into every program.
Categories
| Category | Purpose | Examples |
|---|---|---|
| Creational | How objects are created | Singleton, Factory, Builder |
| Structural | How objects are composed | Adapter, Decorator, Facade |
| Behavioural | How objects interact | Strategy, Observer, Command |
Singleton
One instance for the whole application. Prefer an enum or dependency injection.
public enum Config {
INSTANCE;
private final java.util.Properties props = new java.util.Properties();
public String get(String key) { return props.getProperty(key); }
}
// Config.INSTANCE.get("db.url");
Factory
interface Shape { double area(); }
record Circle(double r) implements Shape { public double area() { return Math.PI * r * r; } }
record Square(double s) implements Shape { public double area() { return s * s; } }
class ShapeFactory {
static Shape create(String type, double size) {
return switch (type) {
case "circle" -> new Circle(size);
case "square" -> new Square(size);
default -> throw new IllegalArgumentException(type);
};
}
}
Builder
Readable construction of objects with many optional fields.
public class Pizza {
private final String size; private final boolean cheese; private final boolean olives;
private Pizza(Builder b) { size = b.size; cheese = b.cheese; olives = b.olives; }
public static class Builder {
private String size = "medium"; private boolean cheese; private boolean olives;
public Builder size(String s) { this.size = s; return this; }
public Builder cheese(boolean c) { this.cheese = c; return this; }
public Builder olives(boolean o) { this.olives = o; return this; }
public Pizza build() { return new Pizza(this); }
}
}
// new Pizza.Builder().size("large").cheese(true).build();
Strategy
Swap algorithms at runtime. In modern Java, a lambda often replaces the class.
interface DiscountStrategy { double apply(double amount); }
class Checkout {
private final DiscountStrategy discount;
Checkout(DiscountStrategy discount) { this.discount = discount; }
double total(double amount) { return discount.apply(amount); }
}
new Checkout(a -> a * 0.9).total(100); // 10% off
Observer
import java.util.*;
import java.util.function.Consumer;
class EventBus {
private final List<Consumer<String>> listeners = new ArrayList<>();
void subscribe(Consumer<String> l) { listeners.add(l); }
void publish(String event) { listeners.forEach(l -> l.accept(event)); }
}
Decorator
Add behaviour by wrapping an object with the same interface (Java I/O streams are built this way: new BufferedReader(new FileReader(...))).
NOTE
Patterns have costs: extra classes and indirection. Use them when they remove real duplication or coupling, and prefer simple code first.