Skip to content

Design Patterns in Java

A design pattern names a recurring design problem, its context, a collaboration structure, and consequences. It is not a code template and should not be added when a direct implementation is clearer.

Pattern families

Family Purpose Examples
Creational Control object creation Factory Method, Abstract Factory, Builder
Structural Compose types and objects Adapter, Decorator, Composite, Proxy
Behavioral Distribute algorithms and responsibilities Strategy, Observer, Command, State, Template Method

Strategy

Strategy makes a replaceable algorithm an explicit collaborator.

@FunctionalInterface
interface ShippingCost {
    Money calculate(Parcel parcel);
}

final class Checkout {
    private final ShippingCost shipping;

    Checkout(ShippingCost shipping) {
        this.shipping = shipping;
    }

    Money total(Order order, Parcel parcel) {
        return order.subtotal().add(shipping.calculate(parcel));
    }
}

A lambda can implement a single-method strategy. Comparator<T> is a standard library example of an interchangeable ordering strategy.

Factory and Builder

A factory centralizes a creation decision when callers should depend on a role, not a concrete construction process. A builder accumulates optional or staged construction data and validates before producing the final object.

Do not create a factory that merely renames new with no hidden decision, invariant, lifecycle, or substitutable product.

Adapter, Decorator, and Proxy

  • Adapter translates one interface into the interface a client requires.
  • Decorator adds behavior while preserving the wrapped object's contract and usually delegates the same role.
  • Proxy controls access to another object, possibly for remoting, laziness, security, transactions, or observation.

These patterns can have similar wrapper shapes but different intent. Java's stream classes contain decorator-like compositions; dynamic proxies provide a mechanism for proxy implementations.

Observer

Observers subscribe to notifications from a subject. This reduces direct knowledge of receivers but introduces delivery, ordering, failure, lifetime, and reentrancy questions. In-process observation is not automatically durable messaging.

Command and State

A command represents a request as an object or function, enabling queues, logging, retries, undo, or scheduling where semantics permit. State moves state-dependent behavior into explicit state objects, avoiding large conditionals when transitions and behavior are sufficiently complex.

Template Method versus composition

Template Method defines an algorithm skeleton in a base class and lets subclasses override selected steps. Strategy expresses variation through composition and is often more flexible in Java. Template Method remains useful when the skeleton and extension hooks form a stable inheritance contract.

Modern Java effects

Records reduce boilerplate for immutable data carriers, sealed hierarchies bound known variants, lambdas simplify strategies and commands, and pattern matching can make closed algebraic-style models clearer. These features change pattern implementations; they do not eliminate the underlying design problems.

Pattern selection questions

  1. What concrete variation or collaboration problem exists?
  2. Is a language or library feature already a simpler solution?
  3. Which dependencies and lifecycle obligations does the pattern introduce?
  4. How will failure, concurrency, and testing work?
  5. Will another maintainer recognize the intent without reading every class?

Exercises

  1. Implement notification channels as strategies, then compare with Observer.
  2. Wrap a repository with a caching decorator and define the cache invalidation semantics.
  3. Explain how Adapter and Decorator differ even if both contain one delegate.