Programming & Java

Java Design Patterns: Complete Guide (Creational, Structural, Behavioral) with Examples & Diagrams — 2026

Learn all 23 Gang of Four design patterns in Java: creational (Singleton, Factory, Abstract Factory, Builder, Prototype), structural (Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy), and behavioral (Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor) — with Mermaid diagrams and detailed code examples.

29 March 2026
42 min read
Java & OOP

Design patterns are reusable solutions to recurring object-oriented design problems. The classic Gang of Four catalog groups 23 patterns into creational, structural, and behavioral families. This guide explains each pattern with a fuller rationale (intent, when to use it, and tradeoffs), a Mermaid diagram, and a focused Java example you can adapt in production code.

Overview and taxonomy

Creational patterns focus on flexible object creation. Structural patterns compose classes and objects into larger structures. Behavioral patterns define communication, responsibility assignment, and algorithms between objects. The diagram below maps every pattern in this article to its family.

Behavioral - communication and responsibility

Chain of Responsibility

Command

Interpreter

Iterator

Mediator

Memento

Observer

State

Strategy

Template Method

Visitor

Structural - composition and relationships

Adapter

Bridge

Composite

Decorator

Facade

Flyweight

Proxy

Creational - object creation

Singleton

Factory Method

Abstract Factory

Builder

Prototype

GoF design patterns grouped by intent

Creational design patterns

Use these when you need to control how and when objects are created: single instances, families of products, fluent construction, or cloning without tight coupling to concrete classes.

Singleton

Ensures a class has at most one instance and provides a global point of access to it. That is useful when exactly one coordinator is needed—for example a connection pool facade, a configuration registry, or hardware access—without scattering `new` across the codebase.

Singleton is easy to overuse: hidden global state complicates testing and can hide dependencies. Prefer dependency injection and explicit lifecycle ownership when you can; reach for singleton only when the domain truly requires a single shared instance.

In Java, prefer the initialization-on-demand holder idiom for lazy, thread-safe creation without synchronizing on every `getInstance()` call. Alternatives include enums (for simple stateless singletons) or a DI framework managing a single bean.

Client code

getInstance

Holder class

Single instance

Singleton — structure
Singleton.java (initialization-on-demand holder)
public final class DatabaseConnection {

  private DatabaseConnection() {}

  private static final class Holder {
    static final DatabaseConnection INSTANCE = new DatabaseConnection();
  }

  public static DatabaseConnection getInstance() {
    return Holder.INSTANCE;
  }

  public void query(String sql) {
    System.out.println("Execute: " + sql);
  }
}

Factory Method

Defines an interface for creating an object, but lets subclasses (or dedicated factory types) decide which concrete class to instantiate. The client depends on the creator abstraction and the product interface, not on specific `new` calls scattered through the app.

Use it when construction rules vary by context—export formats, parsers for different file types, or plugin-style backends—while keeping the workflow (validate, build, post-process) stable in the base type.

Factory Method pairs well with `switch` expressions, sealed hierarchies, or service-loader style discovery. It avoids giant if/else trees in callers and localizes the mapping from keys or configuration to concrete implementations.

Creator

factoryMethod

ConcreteProductA

ConcreteProductB

Client

Factory Method — structure
Factory Method — document exporters
public abstract class DocumentExporter {
  public final void export(Path path) throws IOException {
    byte[] data = render();
    Files.write(path, data);
  }

  protected abstract byte[] render();

  public static DocumentExporter forType(String type) {
    return switch (type.toLowerCase()) {
      case "pdf" -> new PdfExporter();
      case "html" -> new HtmlExporter();
      default -> throw new IllegalArgumentException(type);
    };
  }
}

final class PdfExporter extends DocumentExporter {
  @Override protected byte[] render() { return "%PDF-1.4".getBytes(StandardCharsets.UTF_8); }
}

final class HtmlExporter extends DocumentExporter {
  @Override protected byte[] render() { return "<html></html>".getBytes(StandardCharsets.UTF_8); }
}

Abstract Factory

Provides an interface for creating families of related objects without specifying their concrete classes. Products in one family are designed to work together: a Windows factory yields Windows buttons and checkboxes; a Mac factory yields Mac widgets, so you never mix incompatible pairs.

Reach for Abstract Factory when you have two or more dimensions of variation (look-and-feel, cloud vendor, database dialect) and you want to swap the whole set at once. It keeps “which theme or stack am I on?” in one place.

Tradeoff: adding a new product type touches every concrete factory. For smaller systems, a single factory with clear packages may suffice; for large plugin ecosystems, combine with dependency injection or configuration-driven registration.

AbstractFactory

createButton

createCheckbox

WinButton

MacButton

Abstract Factory — structure
Abstract Factory — GUI widgets
public interface WidgetFactory {
  Button createButton();
  Checkbox createCheckbox();
}

public final class WindowsWidgetFactory implements WidgetFactory {
  @Override public Button createButton() { return new WinButton(); }
  @Override public Checkbox createCheckbox() { return new WinCheckbox(); }
}

public final class MacWidgetFactory implements WidgetFactory {
  @Override public Button createButton() { return new MacButton(); }
  @Override public Checkbox createCheckbox() { return new MacCheckbox(); }
}

public interface Button { void paint(); }
public interface Checkbox { void paint(); }

record WinButton() implements Button { @Override public void paint() {} }
record MacButton() implements Button { @Override public void paint() {} }
record WinCheckbox() implements Checkbox { @Override public void paint() {} }
record MacCheckbox() implements Checkbox { @Override public void paint() {} }

Builder

Separates the step-by-step construction of a complex object from its final representation. The product can enforce invariants at `build()` time—required fields set, combinations validated—before any immutable object is exposed.

Fluent builders (methods that `return this`) read like prose when you have many optional parameters, defaults, or ordered steps. They are a common alternative to telescoping constructors and long parameter lists.

In Java, nested static builder classes, records with compact constructors, or libraries like Immutables and Lombok `@Builder` follow the same idea. A Director is optional: use it when the assembly sequence itself is a reusable recipe.

Director optional

Builder

setA

setB

build

Product

Builder — structure
Builder — HTTP request config
public final class HttpRequest {
  private final String method;
  private final String url;
  private final Map<String, String> headers;
  private final String body;

  private HttpRequest(Builder b) {
    this.method = b.method;
    this.url = b.url;
    this.headers = Map.copyOf(b.headers);
    this.body = b.body;
  }

  public static class Builder {
    private String method = "GET";
    private String url;
    private final Map<String, String> headers = new LinkedHashMap<>();
    private String body = "";

    public Builder url(String u) { this.url = u; return this; }
    public Builder method(String m) { this.method = m; return this; }
    public Builder header(String k, String v) { headers.put(k, v); return this; }
    public Builder body(String b) { this.body = b; return this; }

    public HttpRequest build() {
      if (url == null || url.isBlank()) throw new IllegalStateException("url");
      return new HttpRequest(this);
    }
  }

  @Override public String toString() {
    return method + " " + url + " headers=" + headers + " body=" + body;
  }
}

Prototype

Creates new objects by copying an existing prototype instead of building from scratch. That fits expensive-to-configure templates: reports, game levels, default document layouts, or graph nodes where most fields stay the same.

The hard part is deep vs shallow copy and shared mutable state. If two clones mutate the same nested collection, you get subtle bugs—copy logic must match your ownership rules.

In Java, `Cloneable`/`clone()` is fragile and rarely recommended for public APIs. Prefer copy constructors, static factory methods like `copyOf`, or serialization-based cloning when appropriate, and document which fields are shared vs duplicated.

Prototype

clone or copy

Copy 1

Copy 2

Prototype — structure
Prototype — copyable report template
public final class ReportTemplate implements Cloneable {
  private String title;
  private final List<String> sections = new ArrayList<>();

  public ReportTemplate(String title) { this.title = title; }

  public void addSection(String s) { sections.add(s); }

  @Override
  public ReportTemplate clone() {
    try {
      ReportTemplate t = (ReportTemplate) super.clone();
      t.sections.addAll(this.sections);
      return t;
    } catch (CloneNotSupportedException e) {
      throw new AssertionError(e);
    }
  }
}

// Usage: ReportTemplate draft = base.clone(); draft.addSection("Appendix");

Structural design patterns

These patterns help you assemble objects into flexible hierarchies, adapters, proxies, and lightweight compositions without changing the right abstractions at the wrong layer.

Adapter

Converts the interface of a class (the adaptee) into another interface that clients already expect (the target). Legacy libraries, third-party SDKs, or odd-shaped APIs can be wrapped so the rest of your code speaks one consistent dialect.

Class adapters (inheritance) and object adapters (composition) both work; composition is usually more flexible in Java because you adapt a delegate instance you do not control the source of.

Adapters are intentionally thin: translate calls and types, avoid smuggling new business rules. If you keep growing “the adapter,” consider a facade or a proper port in hexagonal architecture instead.

Client expects Target

Adapter

Adaptee legacy API

Adapter — structure
Adapter — legacy CSV reader to Iterator
public interface RowReader {
  boolean hasNext();
  String[] readRow();
}

public final class LegacyCsvParser {
  private final List<String> lines;
  private int i;
  public LegacyCsvParser(List<String> lines) { this.lines = lines; }
  public boolean more() { return i < lines.size(); }
  public String[] nextFields() { return lines.get(i++).split(","); }
}

public final class CsvParserAdapter implements RowReader {
  private final LegacyCsvParser parser;
  public CsvParserAdapter(LegacyCsvParser parser) { this.parser = parser; }
  @Override public boolean hasNext() { return parser.more(); }
  @Override public String[] readRow() { return parser.nextFields(); }
}

Bridge

Decouples an abstraction (what clients use, e.g. shapes) from an implementation (how it is rendered, logged, or persisted) so each hierarchy can evolve independently. You compose “abstraction + implementation” at runtime instead of subclassing every combination.

Without Bridge, `CircleVector`, `CircleRaster`, `SquareVector`, … multiply quickly. With Bridge, `Circle` holds a `Renderer`; new renderers or new shapes add one class each.

It is similar in spirit to Strategy, but Bridge names a structural relationship: the abstraction owns a long-lived implementation dimension. Use it when both sides are first-class extension points in your domain model.

Abstraction

Implementation interface

Impl A

Impl B

Bridge — structure
Bridge — shapes and renderers
public interface Renderer {
  void renderCircle(float r);
}

public abstract class Shape {
  protected final Renderer renderer;
  protected Shape(Renderer renderer) { this.renderer = renderer; }
  public abstract void draw();
}

public final class Circle extends Shape {
  private final float radius;
  public Circle(Renderer renderer, float radius) {
    super(renderer);
    this.radius = radius;
  }
  @Override public void draw() { renderer.renderCircle(radius); }
}

public final class VectorRenderer implements Renderer {
  @Override public void renderCircle(float r) {
    System.out.println("Vector circle r=" + r);
  }
}

Composite

Composes objects into tree structures to represent part-whole hierarchies: folders and files, UI panels and widgets, org charts, or AST nodes. Clients call the same operations on leaves and composites—size, render, validate—without `instanceof` ladders.

The composite implements the child interface and delegates to a collection of children, often with add/remove for internal nodes only. That uniformity simplifies recursive algorithms (sum sizes, find nodes, serialize).

Watch for operations that only make sense on leaves or only on composites; you can split interfaces (e.g. `CompositeNode` vs `Leaf`) or use default no-ops with care to avoid violating the Liskov substitution principle.

Composite

Leaf

Composite

Leaf

Leaf

Composite — structure
Composite — file system nodes
public interface FileNode {
  String getName();
  long getSize();
}

public final class File implements FileNode {
  private final String name;
  private final long size;
  public File(String name, long size) { this.name = name; this.size = size; }
  @Override public String getName() { return name; }
  @Override public long getSize() { return size; }
}

public final class Directory implements FileNode {
  private final String name;
  private final List<FileNode> children = new ArrayList<>();
  public Directory(String name) { this.name = name; }
  public void add(FileNode n) { children.add(n); }
  @Override public String getName() { return name; }
  @Override public long getSize() {
    return children.stream().mapToLong(FileNode::getSize).sum();
  }
}

Decorator

Attaches extra responsibilities to an object by wrapping it in decorators that share the same interface as the core component. Behavior is composed at runtime (milk + sugar + coffee) instead of subclassing every combination.

Decorators are transparent: callers still use the component type. Ordering can matter (compression then encryption vs the reverse), so document or enforce a sensible stack.

Contrast with Proxy (usually one wrapper controlling access) and Composite (tree structure). Decorator chains stay linear and each layer adds a small cross-cutting concern: I/O filters in `java.io` are the classic JDK example.

Client

Decorator

Decorator

Component

Decorator — structure
Decorator — coffee add-ons
public interface Coffee {
  double cost();
  String description();
}

public final class SimpleCoffee implements Coffee {
  @Override public double cost() { return 2.0; }
  @Override public String description() { return "Coffee"; }
}

public abstract class CoffeeDecorator implements Coffee {
  protected final Coffee wrappee;
  protected CoffeeDecorator(Coffee c) { this.wrappee = c; }
  @Override public double cost() { return wrappee.cost(); }
  @Override public String description() { return wrappee.description(); }
}

public final class Milk extends CoffeeDecorator {
  public Milk(Coffee c) { super(c); }
  @Override public double cost() { return super.cost() + 0.5; }
  @Override public String description() { return super.description() + ", milk"; }
}

Facade

Provides a single, higher-level entry point that coordinates several subsystem classes behind one or a few methods. Callers get “watch movie” instead of juggling amplifier, projector, and player in the right order.

Facades do not forbid direct subsystem access; they document the typical workflow and reduce coupling of application code to low-level APIs. They are common at module boundaries and in integration layers.

Keep facades from becoming god objects: if every new feature lands in one class, split by use case or introduce smaller facades. The goal is clarity and stable orchestration, not hiding an entire domain inside one type.

Client

Facade

Subsystem A

Subsystem B

Subsystem C

Facade — structure
Facade — home theater one-button
public final class Amplifier { public void on() {} public void setVolume(int v) {} }
public final class DvdPlayer { public void on() {} public void play(String m) {} }
public final class Projector { public void on() {} public void wideScreen() {} }

public final class HomeTheaterFacade {
  private final Amplifier amp;
  private final DvdPlayer dvd;
  private final Projector proj;

  public HomeTheaterFacade(Amplifier amp, DvdPlayer dvd, Projector proj) {
    this.amp = amp; this.dvd = dvd; this.proj = proj;
  }

  public void watchMovie(String movie) {
    amp.on(); amp.setVolume(5);
    proj.on(); proj.wideScreen();
    dvd.on(); dvd.play(movie);
  }
}

Flyweight

Uses sharing to support large numbers of fine-grained objects without storing duplicate immutable data for each instance. Intrinsic state (the glyph for “A”, icon bitmap metadata) lives once in a flyweight; extrinsic state (position, font size, color) is passed in when you draw or compute.

Effective when many objects differ only slightly and memory or allocation pressure matters—text editors, maps with repeated tiles, game entity visuals.

You need a factory or pool to hand out canonical flyweights (often keyed by identity). Thread safety and lifecycle (when to evict from the pool) are design choices; flyweights must be immutable or safely published to avoid races.

FlyweightFactory

Flyweight shared

Context A

Context B

Flyweight — structure
Flyweight — character glyphs in an editor
public final class GlyphFlyweight {
  private final char symbol;
  public GlyphFlyweight(char symbol) { this.symbol = symbol; }

  public void drawAt(int x, int y) {
    System.out.println(symbol + " at " + x + "," + y);
  }
}

public final class GlyphFactory {
  private final Map<Character, GlyphFlyweight> pool = new HashMap<>();

  public GlyphFlyweight glyphFor(char c) {
    return pool.computeIfAbsent(c, GlyphFlyweight::new);
  }
}

Proxy

Provides a surrogate that stands in for another object and controls access to it: defer expensive construction (virtual proxy), add logging or security checks, hide remote or async details, or count references.

The proxy implements the same interface as the real subject so clients stay unaware. Unlike Decorator, the emphasis is on managing access or lifetime, not stacking open-ended features—though in practice boundaries blur.

Java dynamic proxies (`java.lang.reflect.Proxy`), RMI stubs, and lazy JPA associations are familiar examples. Keep proxy logic thin; heavy domain rules belong in the real subject or a dedicated service layer.

may defer

Client

Proxy

RealSubject

Proxy — structure
Proxy — lazy image loading
public interface Image {
  void display();
}

public final class HighResImage implements Image {
  private final String path;
  public HighResImage(String path) {
    this.path = path;
    loadFromDisk();
  }
  private void loadFromDisk() { System.out.println("Loading " + path); }
  @Override public void display() { System.out.println("Show " + path); }
}

public final class ImageProxy implements Image {
  private final String path;
  private HighResImage real;

  public ImageProxy(String path) { this.path = path; }

  @Override
  public void display() {
    if (real == null) real = new HighResImage(path);
    real.display();
  }
}

Behavioral design patterns

Behavioral patterns govern interaction: algorithms that can vary, notifications, undo, state machines, and visitors over object structures. They keep collaborators loosely coupled.

Chain of Responsibility

Passes a request along a chain of handlers until one of them handles it—or the request falls off the end. The sender only talks to the head; it does not know which handler will succeed.

Useful for filters, middleware, approval workflows, and support tiers where rules evolve and you want to add or reorder steps without changing the client. Each handler decides “handle or forward.”

Avoid deep chains with duplicated logic; extract shared preconditions. In Java, servlet filters, Spring `HandlerInterceptor`s, and validation pipelines follow the same shape. Consider breaking the chain if debugging “who handled this?” becomes painful without observability.

pass

pass

Request

Handler 1

Handler 2

Handler 3

Chain of Responsibility — structure
Chain of Responsibility — support escalation
public abstract class SupportHandler {
  private SupportHandler next;

  public SupportHandler setNext(SupportHandler next) {
    this.next = next;
    return next;
  }

  public final void handle(String issue) {
    if (canHandle(issue)) resolve(issue);
    else if (next != null) next.handle(issue);
    else System.out.println("Escalate: " + issue);
  }

  protected abstract boolean canHandle(String issue);
  protected abstract void resolve(String issue);
}

public final class L1 extends SupportHandler {
  @Override protected boolean canHandle(String issue) { return issue.contains("password"); }
  @Override protected void resolve(String issue) { System.out.println("L1 fixed: " + issue); }
}

Command

Encapsulates a request as an object with a uniform interface (`execute`, often `undo`). The invoker triggers commands without knowing receiver details; commands can be queued, logged, retried, or composed into macros.

Ideal for undo/redo, transactional steps, job queues, and GUI actions where the same operation must be scheduled or replayed. Macro recording is literally a list of command objects.

Balance granularity: too many tiny commands add overhead; too few lose flexibility. Immutable command objects with captured parameters simplify history stacks and thread-safe handoff to worker threads.

Invoker

Command

Receiver

Command — structure
Command — text editor with undo
public interface Command {
  void execute();
  void undo();
}

public final class TypeCommand implements Command {
  private final StringBuilder doc;
  private final String text;
  public TypeCommand(StringBuilder doc, String text) {
    this.doc = doc; this.text = text;
  }
  @Override public void execute() { doc.append(text); }
  @Override public void undo() {
    int len = doc.length() - text.length();
    doc.delete(len, doc.length());
  }
}

public final class Macro {
  private final Deque<Command> history = new ArrayDeque<>();

  public void run(Command c) {
    c.execute();
    history.push(c);
  }

  public void undoLast() {
    if (!history.isEmpty()) history.pop().undo();
  }
}

Interpreter

Defines a representation for a language’s grammar along with an interpreter that evaluates sentences in that language. Expressions are usually modeled as a tree of nodes, each implementing `interpret(context)`.

Best for small, controlled DSLs—query builders, rule engines, configuration formulas—where the grammar is stable and performance is acceptable. Large or evolving grammars often move to parser generators or bytecode instead.

The pattern is clear but can grow unwieldy: every new syntax form needs new expression classes. Pair with Visitor if you add many operations over the same AST without touching every node type repeatedly.

Expression

AddExpression

NumberExpression

left Expr

right Expr

Interpreter — structure
Interpreter — simple arithmetic
public interface Expr {
  int interpret(Map<String, Integer> context);
}

public record NumberExpr(int value) implements Expr {
  @Override public int interpret(Map<String, Integer> ctx) { return value; }
}

public record VariableExpr(String name) implements Expr {
  @Override public int interpret(Map<String, Integer> ctx) {
    return ctx.getOrDefault(name, 0);
  }
}

public record AddExpr(Expr left, Expr right) implements Expr {
  @Override public int interpret(Map<String, Integer> ctx) {
    return left.interpret(ctx) + right.interpret(ctx);
  }
}

Iterator

Provides sequential access to elements of an aggregate (list, set, tree, custom collection) without exposing its internal storage shape. Clients depend on `Iterator` or `Iterable`, not on arrays versus linked lists versus maps.

Java’s enhanced for-loop, streams, and `Spliterator` build on the same idea: uniform traversal with optional removal (`Iterator.remove()`), fail-fast behavior, and clear separation of external iteration vs internal iterators.

When you expose iterators from your own types, document concurrency: concurrent modification during traversal may require copy-on-write, locking, or snapshot iterators depending on consistency needs.

Aggregate

Iterator

next

hasNext

Iterator — structure
Iterator — custom collection
import java.util.*;

public final class BookShelf implements Iterable<String> {
  private final List<String> books = new ArrayList<>();

  public void add(String title) { books.add(title); }

  @Override
  public Iterator<String> iterator() {
    return books.iterator();
  }
}

// Java's enhanced for-loop uses Iterator under the hood:
// for (String b : shelf) { ... }

Mediator

Centralizes complex many-to-many communication in a mediator object. Colleagues notify the mediator instead of calling each other directly; the mediator decides who needs what update, reducing tangled references.

Chat rooms, dialog wizards, and air-traffic-style coordination are classic fits. UI frameworks often use mediators or presenters to keep widgets from knowing about every other widget.

Risk: the mediator can absorb too much logic and become a bottleneck. Keep it focused on routing and orchestration; domain rules still belong in the colleagues or domain services. Extract sub-mediators if workflows split cleanly.

Mediator

Colleague 1

Colleague 2

Colleague 3

Mediator — structure
Mediator — chat room
public interface ChatMediator {
  void broadcast(String from, String message);
}

public final class ChatRoom implements ChatMediator {
  private final List<User> users = new ArrayList<>();

  public void join(User u) { users.add(u); }

  @Override
  public void broadcast(String from, String message) {
    for (User u : users) {
      if (!u.getName().equals(from)) u.receive(from, message);
    }
  }
}

public final class User {
  private final String name;
  private final ChatMediator room;

  public User(String name, ChatMediator room) {
    this.name = name; this.room = room;
  }
  public String getName() { return name; }
  public void send(String msg) { room.broadcast(name, msg); }
  public void receive(String from, String msg) {
    System.out.println(name + " got from " + from + ": " + msg);
  }
}

Memento

Captures an object’s internal state into a memento object that can be stored externally and later used to restore the originator to that state. Encapsulation stays intact if mementos are opaque to everyone except the originator.

Undo stacks, checkpoints in editors, game save slots, and transactional drafts are natural uses. The caretaker holds mementos but should not inspect or mutate their internals.

Memory cost can grow with deep histories; consider limits, compression, or storing deltas instead of full snapshots. In Java, immutable memento records pair cleanly with a deque of history entries.

Originator

Memento

Caretaker

Memento — structure
Memento — editor snapshot
public final class Editor {
  private String content = "";

  public void type(String text) { content += text; }

  public Memento save() { return new Memento(content); }

  public void restore(Memento m) { this.content = m.state(); }

  public String getContent() { return content; }

  public record Memento(String state) {}
}

public final class History {
  private final Deque<Editor.Memento> stack = new ArrayDeque<>();

  public void push(Editor.Memento m) { stack.push(m); }

  public Editor.Memento pop() { return stack.pop(); }
}

Observer

Defines a one-to-many dependency between subjects and observers: when the subject’s state changes, all registered observers are notified—push, pull, or hybrid—without the subject knowing concrete observer types.

Event buses, model-view separation, reactive UIs, and domain events use this shape. Java’s `PropertyChangeListener`, Swing listeners, and reactive streams generalize the idea with richer backpressure and composition.

Watch for notification storms, re-entrancy (observer updates trigger more updates), and leaks if observers are not unregistered. Weak references or lifecycle-aware registration help in long-lived UIs and server contexts.

notify

notify

Subject

Observer 1

Observer 2

Observer — structure
Observer — stock price
public interface StockListener {
  void onPriceChange(String symbol, double price);
}

public final class StockTicker {
  private final Map<String, Double> prices = new HashMap<>();
  private final List<StockListener> listeners = new ArrayList<>();

  public void subscribe(StockListener l) { listeners.add(l); }

  public void update(String symbol, double price) {
    prices.put(symbol, price);
    for (StockListener l : listeners) l.onPriceChange(symbol, price);
  }
}

State

Lets an object delegate behavior to a state object; when the state object is swapped, the context’s operations change without huge conditionals. From the outside it can feel as if the object “became” another type.

Replaces sprawling `switch` on enums with cohesive classes per state: valid transitions and actions live next to each other, which scales better as rules grow (workflows, TCP-style connections, game AI modes).

States should be lightweight; pass context in so states can trigger transitions. Avoid cyclic dependencies between state classes—often solved by transitions initiated only from the context or a small transition table.

Context

State

State A

State B

State — structure
State — TCP-style connection
public interface ConnectionState {
  void open(Connection ctx);
  void close(Connection ctx);
}

public final class ClosedState implements ConnectionState {
  @Override public void open(Connection ctx) {
    System.out.println("Opening...");
    ctx.setState(new OpenState());
  }
  @Override public void close(Connection ctx) { /* no-op */ }
}

public final class OpenState implements ConnectionState {
  @Override public void open(Connection ctx) { /* already open */ }
  @Override public void close(Connection ctx) {
    System.out.println("Closing...");
    ctx.setState(new ClosedState());
  }
}

public final class Connection {
  private ConnectionState state = new ClosedState();
  public void setState(ConnectionState s) { this.state = s; }
  public void open() { state.open(this); }
  public void close() { state.close(this); }
}

Strategy

Defines a family of algorithms behind a common interface, encapsulates each variant, and lets clients choose at runtime which strategy to use. The context delegates the variable part without subclass explosion.

Sorting policies, pricing rules, compression codecs, and payment methods are typical examples. Strategy differs from State mainly by intent: strategies are often peer alternatives selected by configuration; states model internal lifecycle.

In Java, lambdas and method references can implement small strategies inline; use named classes when strategies carry substantial state or need testing in isolation. Inject strategies via constructors for testability.

Context

Strategy interface

Strategy A

Strategy B

Strategy — structure
Strategy — payment methods
public interface PaymentStrategy {
  void pay(BigDecimal amount);
}

public final class CreditCardStrategy implements PaymentStrategy {
  @Override public void pay(BigDecimal amount) {
    System.out.println("Paid " + amount + " by card");
  }
}

public final class PayPalStrategy implements PaymentStrategy {
  @Override public void pay(BigDecimal amount) {
    System.out.println("Paid " + amount + " by PayPal");
  }
}

public final class Checkout {
  private PaymentStrategy strategy = new CreditCardStrategy();

  public void setPaymentStrategy(PaymentStrategy s) { this.strategy = s; }

  public void checkout(BigDecimal total) {
    strategy.pay(total);
  }
}

Template Method

Defines the skeleton of an algorithm in a final template method: fixed steps and their order live in the base class; specific steps are hooks or abstract methods implemented by subclasses.

Frameworks use this to control extension points—JUnit test lifecycle, servlet-style pipelines—while enforcing invariant structure. It inverts control: the framework calls your overrides at the right times.

Contrast with Strategy (composition replaces subclassing for the variable part). Template Method couples the algorithm to inheritance; prefer Strategy or functional interfaces when you need runtime swapping without new subclasses.

templateMethod

step1

step2 hook

step3

Template Method — structure
Template Method — data export pipeline
public abstract class DataExporter {

  public final void export(Path out) throws IOException {
    byte[] header = buildHeader();
    byte[] body = buildBody();
    byte[] footer = buildFooter();
    Files.write(out, concat(header, body, footer));
  }

  protected abstract byte[] buildHeader();
  protected abstract byte[] buildBody();

  protected byte[] buildFooter() { return new byte[0]; }

  private static byte[] concat(byte[]... parts) throws IOException {
    try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
      for (byte[] p : parts) bos.write(p);
      return bos.toByteArray();
    }
  }
}

Visitor

Separates an algorithm over an object structure from the element classes themselves. Each element implements `accept(visitor)`; the visitor dispatches to type-specific `visit` methods—double dispatch in classic OO form.

Add a new operation by adding a visitor class without editing every element—provided the element set is fairly stable. If you add new node types often, every visitor must grow a new method (expression problem).

Compilers (AST walks), document export pipelines, and validation passes fit well. In Java, sealed hierarchies and exhaustive `switch` can reduce boilerplate compared to large visitor interfaces when the type set is closed.

Visitor

Element A

Element B

accept Visitor

Visitor — structure
Visitor — document AST
public interface DocVisitor {
  void visit(Paragraph p);
  void visit(Heading h);
}

public interface DocNode {
  void accept(DocVisitor v);
}

public final class Paragraph implements DocNode {
  private final String text;
  public Paragraph(String text) { this.text = text; }
  @Override public void accept(DocVisitor v) { v.visit(this); }
  public String getText() { return text; }
}

public final class Heading implements DocNode {
  private final String text;
  public Heading(String text) { this.text = text; }
  @Override public void accept(DocVisitor v) { v.visit(this); }
  public String getText() { return text; }
}

public final class WordCountVisitor implements DocVisitor {
  private int words;

  @Override public void visit(Paragraph p) { words += p.getText().split("\\s+").length; }
  @Override public void visit(Heading h) { words += h.getText().split("\\s+").length; }

  public int getWords() { return words; }
}


?

Frequently asked questions

The GoF catalog groups patterns into creational (how objects are instantiated), structural (how classes and objects compose into larger structures), and behavioral (how objects collaborate and responsibilities are assigned). There are 5 creational, 7 structural, and 11 behavioral patterns in the classic book.

No. Patterns are tools for common problems. Overusing them adds complexity. Prefer simple code until you see a real problem a pattern solves — for example, use Strategy when algorithms must vary, or Observer when many objects need to react to a single change.

Patterns often embody SOLID ideas: Single Responsibility (e.g. Command), Open/Closed (Decorator, Strategy), Liskov Substitution (polymorphic pattern roles), Interface Segregation (small interfaces), and Dependency Inversion (depend on abstractions). Learning both together helps you judge when to refactor toward a pattern.

Yes. Frameworks like Spring use patterns heavily (Proxy, Template Method, Factory, Observer). Understanding patterns helps you read framework code, configure beans correctly, and avoid fighting the framework with anti-patterns.

Factory Method lets a subclass define which concrete class to instantiate (one product type per subclass). Abstract Factory provides an interface for creating families of related objects (e.g. all Windows widgets vs all Mac widgets) without naming concrete classes.

Refactor small projects: introduce a Strategy for payment logic, a Builder for configuration objects, or an Observer for event notification. You can also study open-source Java libraries and map their code to pattern names.

Back to all articlesPublished · FreeToolSuite Blog

FreeToolSuite — 100% free online tools, no sign-up required.

© 2026 FreeToolSuite. MIT.

We use cookies and Google Analytics to understand how visitors use our site so we can improve your experience. No personal data is collected. You can accept or decline analytics cookies.