Design Patterns in Java
Design patterns are reusable approaches to recurring software-design problems. They are not code templates to apply everywhere; they are vocabulary for discussing trade-offs in object creation, structure, and behavior.
Why Learn Design Patterns?
- They give developers a shared design vocabulary.
- They help separate responsibilities.
- They reduce coupling when applied appropriately.
- They make common trade-offs easier to recognize.
Pattern Categories
| Category | Focus | Examples |
|---|---|---|
| Creational | How objects are created | Singleton, Factory, Builder |
| Structural | How classes and objects are composed | Adapter, Decorator, Facade |
| Behavioral | How objects communicate and share responsibilities | Observer, Strategy, Command |
1. Singleton Pattern
Singleton restricts a class to one shared instance and provides a global access point to that instance.
public final class AppConfig {
private static final AppConfig INSTANCE =
new AppConfig();
private AppConfig() {
}
public static AppConfig getInstance() {
return INSTANCE;
}
}This eager-initialization version is simple and thread-safe because class initialization is handled by the JVM.
When it may fit
- A truly process-wide stateless service or configuration holder.
- A resource that conceptually must have one shared instance.
Caution
Singleton can become hidden global state, making testing and dependency management harder. Dependency injection is often preferable for application services.
2. Factory Pattern
A factory centralizes object creation so calling code depends on an abstraction instead of concrete classes.
interface Shape {
void draw();
}
class Circle implements Shape {
public void draw() {
System.out.println("Drawing a circle");
}
}
class Square implements Shape {
public void draw() {
System.out.println("Drawing a square");
}
}
class ShapeFactory {
public Shape create(String type) {
return switch (type.toLowerCase()) {
case "circle" -> new Circle();
case "square" -> new Square();
default ->
throw new IllegalArgumentException(
"Unknown shape: " + type
);
};
}
}The caller asks for a Shape without needing to know which concrete constructor is used.
3. Observer Pattern
Observer defines a one-to-many relationship. When one object changes, subscribed observers are notified.
interface Observer {
void update(int temperature);
}
class WeatherStation {
private final List<Observer> observers =
new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void setTemperature(int temperature) {
for (Observer observer : observers) {
observer.update(temperature);
}
}
}Observer is common in event systems, GUI listeners, publish-subscribe models, and reactive architectures.
4. MVC Pattern
MVC separates an application into three responsibilities:
- Model — data and domain state.
- View — presentation.
- Controller — handles input and coordinates model/view updates.
class Student {
private final String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class StudentView {
public void printStudentDetails(String name) {
System.out.println("Student: " + name);
}
}
class StudentController {
private final Student model;
private final StudentView view;
public StudentController(
Student model,
StudentView view
) {
this.model = model;
this.view = view;
}
public void updateView() {
view.printStudentDetails(model.getName());
}
}5. Strategy Pattern
Strategy encapsulates interchangeable algorithms behind a common interface.
interface DiscountStrategy {
double apply(double total);
}
class StudentDiscount
implements DiscountStrategy {
public double apply(double total) {
return total * 0.90;
}
}
class Checkout {
private final DiscountStrategy strategy;
public Checkout(DiscountStrategy strategy) {
this.strategy = strategy;
}
public double total(double amount) {
return strategy.apply(amount);
}
}The behavior can change by supplying another strategy object without rewriting the checkout logic.
Patterns and Java Features
| Pattern idea | Useful Java feature |
|---|---|
| Strategy | Interfaces, lambdas, functional interfaces |
| Observer | Listeners, collections, callbacks |
| Factory | Interfaces, sealed types, switch expressions |
| Decorator | Interface composition, delegation |
| Builder | Fluent APIs, immutable objects |
Pattern vs Principle
Patterns are concrete design arrangements. Principles such as SOLID, information hiding, composition over inheritance, and low coupling are broader guidelines that help determine whether a pattern is appropriate.
Common Mistakes
- Using Singleton as a convenient global variable.
- Creating factories for objects that are already simple to construct.
- Adding Observer when direct method calls would be clearer.
- Applying patterns because they are familiar rather than because the design needs them.
- Confusing MVC with one exact class structure; real MVC variants differ across frameworks.
Conclusion
Design patterns are best treated as design vocabulary, not mandatory recipes. Learn the problem each pattern solves, the coupling it introduces, and the alternatives available. Singleton, Factory, Observer, MVC, and Strategy are useful starting points for understanding object-oriented design trade-offs in Java.