Java · Software Design · Reusable Structure

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.
Important: a pattern is useful only when it solves a real design problem. Adding patterns without need can make a program more complicated.

Pattern Categories

CategoryFocusExamples
CreationalHow objects are createdSingleton, Factory, Builder
StructuralHow classes and objects are composedAdapter, Decorator, Facade
BehavioralHow objects communicate and share responsibilitiesObserver, 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 ideaUseful Java feature
StrategyInterfaces, lambdas, functional interfaces
ObserverListeners, collections, callbacks
FactoryInterfaces, sealed types, switch expressions
DecoratorInterface composition, delegation
BuilderFluent 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.