Master

Design Patterns

Apply classic design patterns with idiomatic Java.

What you will see

Circle Square

About 2 min of reading

Try It

Reuse classic design patterns. Build a Singleton that returns one shared instance, and a Factory that hands back different concrete objects through one interface.

1

Create a Singleton: a private constructor, a static instance field, and a getInstance method that always returns the same object. Confirm both calls return the identical instance.

class Main { public static void main(String[] args) { System.out.println(Config.getInstance() == Config.getInstance()); } } class Config { private static final Config INSTANCE = new Config(); private Config() {} public static Config getInstance() { return INSTANCE; } }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
true
The private constructor stops external new calls, so getInstance is the only way in. The instance is created once, lazily at class load.
2

Build a Factory that returns concrete objects through a shared interface. Ask the factory for two shapes and call draw on each, callers only know the Shape interface.

import java.util.Arrays; import java.util.List; class Main { public static void main(String[] args) { List shapes = Arrays.asList("circle", "square"); for (String type : shapes) { createShape(type).draw(); } } static Shape createShape(String type) { switch (type) { case "circle": return new Circle(); case "square": return new Square(); default: throw new IllegalArgumentException(type); } } } interface Shape { void draw(); } class Circle implements Shape { public void draw() { System.out.println("Circle"); } } class Square implements Shape { public void draw() { System.out.println("Square"); } }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
Circle Square
Callers depend only on Shape, so adding new shapes later never touches the calling code, that decoupling is the whole point of the Factory pattern.

Data Flow

Patterns restructure object creation and communication to reduce coupling and enable reuse.
1 Caller requests an object or behaviour
↓
2 Factory/creator returns a concrete type
↓
3 Caller invokes via a shared interface
↓
4 Strategy swaps the algorithm at runtime
Why this works
Creational patterns

Creational patterns deal with object creation: Singleton (one instance), Factory (creates objects), Builder (constructs complex objects step by step), and Prototype (clone existing objects). They decouple client code from concrete classes and make instantiation flexible.

Structural and behavioural

Structural patterns (Adapter, Decorator, Facade) compose classes and objects into larger structures. Behavioural patterns (Observer, Strategy, Command) define how objects communicate and distribute responsibility. Choose the pattern that matches the problem, not the other way around.

Examples

Singleton pattern

One instance only

class Config {
    private static final Config INSTANCE = new Config();

    private Config() {}

    public static Config getInstance() {
        return INSTANCE;
    }
}

The private constructor prevents external instantiation. getInstance returns the single shared instance. Prefer a static factory over the enum approach for simple cases.

Factory pattern

Centralised object creation

public interface Shape {
    void draw();
}

class Circle implements Shape {
    public void draw() { System.out.println("Circle"); }
}

public static Shape createShape(String type) {
    return switch (type) {
        case "circle" -> new Circle();
        default -> throw new IllegalArgumentException(type);
    };
}

A factory keeps callers decoupled from concrete classes. Add new shapes without touching caller code.

Strategy pattern

Swappable algorithms

public interface Strategy {
    int compute(int a, int b);
}

class Add implements Strategy {
    public int compute(int a, int b) { return a + b; }
}

class Calculator {
    private final Strategy strategy;

    Calculator(Strategy s) { this.strategy = s; }
    int operate(int a, int b) { return strategy.compute(a, b); }
}

The Strategy pattern lets algorithms be swapped at runtime. Pass the strategy via constructor for flexibility.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Custom Exceptions and Design
Previous Lesson
Custom Exceptions and Design
Next Lesson
Concurrency and Multithreading
Concurrency and Multithreading →