W3docs

Java Design Patterns Introduction

An introduction to design patterns in Java — what they are and how to use the most common ones.

Java Design Patterns Introduction

A design pattern is a named, reusable solution to a problem that keeps coming up when you build software. Patterns aren't libraries you import or code you copy — they're shapes for arranging classes and objects that experienced developers have settled on over time. Learning them gives you a shared vocabulary: say "we'll use a Factory here" and another Java developer instantly knows roughly what you mean.

Patterns were popularized by the 1994 book Design Patterns by the "Gang of Four," which catalogued 23 of them. You don't need all 23 to start. A handful, applied at the right moment, makes code easier to change without breaking it.

The three families

The classic catalogue splits patterns into three groups by what they help with:

FamilyConcernExamples
CreationalHow objects get madeSingleton, Factory, Builder
StructuralHow objects are composedAdapter, Decorator, Facade
BehavioralHow objects interactStrategy, Observer, Iterator

Java itself is full of these. StringBuilder is a Builder, Iterator is the Iterator pattern, and java.util.logging uses Singletons. You've been using patterns without naming them.

Strategy: swap the algorithm

The Strategy pattern captures a family of interchangeable algorithms behind a common interface, so the calling code can switch between them without changing. Define the interface, write each variant as its own class, and let a context hold whichever one it needs.

interface DiscountStrategy {
    double apply(double price);
}

class PercentOff implements DiscountStrategy {
    private final double percent;
    PercentOff(double percent) { this.percent = percent; }
    public double apply(double price) { return price * (1 - percent / 100); }
}

The context delegates instead of branching on a if/switch chain. Adding a new discount means adding a class — not editing existing code.

Factory: centralize creation

A Factory is a single method (or class) responsible for deciding which concrete type to instantiate. Callers ask for a thing by description and get back an object that fulfils the interface, without knowing the exact class.

static DiscountStrategy forCustomer(String tier) {
    return switch (tier) {
        case "gold"   -> new PercentOff(20);
        case "silver" -> new PercentOff(10);
        default       -> new NoDiscount();
    };
}

The creation logic lives in one place. If the rules change, you edit the factory — every caller keeps working unchanged.

Singleton: exactly one instance

A Singleton guarantees a class has just one instance and gives a global access point to it. In modern Java an enum is the simplest, thread-safe way to write one.

enum Config {
    INSTANCE;
    private final String env = "production";
    public String env() { return env; }
}

// usage
String e = Config.INSTANCE.env();

Use Singletons sparingly — they introduce global state, which makes testing harder. Often a single object handed in through a constructor (dependency injection) is the better choice.

When not to reach for a pattern

Patterns add structure, and structure has a cost. A switch with two cases doesn't need the Strategy pattern; a class you make once doesn't need a Factory. Reach for a pattern when you feel the pain it solves — duplicated branching, scattered new calls, tangled object wiring — not before. Over-applying patterns produces code that's harder to read than the problem it was meant to simplify.

Strategy and Factory together

The runnable example below combines two patterns. DiscountStrategy is the Strategy interface with three implementations; forCustomer is a Factory that picks one. The Checkout context delegates to whatever strategy it holds, so its code never changes as the pricing behaviour varies.

java— editable, runs on the server

What to take from the run:

  • Each tier prints a different total — regular stays $100.00, silver drops to $90.00, gold to $80.00, coupon to $95.00 — even though Checkout.total calls the same single method.
  • The Checkout context never branches on the tier itself; it just delegates to whichever DiscountStrategy it currently holds.
  • The forCustomer Factory is the only place that knows which concrete class maps to which tier, so the selection logic lives in one spot.
  • Swapping behaviour is just setStrategy(...) at runtime — adding a fourth discount would mean a new class plus one case in the factory, with no change to Checkout.
  • The final lines confirm the active strategy: after re-selecting gold, the policy reads 20.0% off and the total is $80.00, proving the context reflects whatever strategy was last set.

Practice

Practice

What problem does the Strategy pattern solve?