Skip to main content

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​

CategoryPurposeExamples
CreationalHow objects are createdSingleton, Factory, Builder
StructuralHow objects are composedAdapter, Decorator, Facade
BehaviouralHow objects interactStrategy, 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.