skip to content

How do classic GoF design patterns show up in the Java standard library? Give concrete JDK examples.

level: middleimportance: should knowfreq 70%

answer

  1. Decorator = java.io stream wrapping
  2. Strategy = Comparator passed to sort
  3. Factory = getInstance(); Builder = StringBuilder
  4. Command = Runnable/Callable to an Executor
  5. Flyweight = Integer.valueOf cache (−128..127)

basics

~20 s

The JDK is full of GoF patterns: BufferedInputStream wrapping a stream is Decorator, Comparator passed to sort is Strategy, Calendar.getInstance() is a Factory, Runnable submitted to an executor is Command, and Iterator/Iterable is the Iterator pattern.

solid answer

~40 s

The 'Gang of Four' (GoF) catalogued 23 reusable design patterns, and the Java standard library implements many of them, so naming JDK examples shows you recognize patterns in the wild. Decorator: the `java.io` stream-wrapping hierarchy — `new BufferedInputStream(new FileInputStream(...))` stacks behaviour. Strategy: passing a `Comparator` to `Collections.sort` swaps the ordering algorithm at runtime, made lightweight by lambdas. Factory Method / simple factory: `Calendar.getInstance()`, `NumberFormat.getInstance()` return an appropriate concrete type. Builder: `StringBuilder`, `Stream.Builder`, `Locale.Builder`. Command: `Runnable`/`Callable` submitted to an `ExecutorService`. Iterator: the `Iterator`/`Iterable` pair behind the for-each loop. Adapter: `Arrays.asList`, `InputStreamReader`. Proxy: `java.lang.reflect.Proxy`. Flyweight: `Integer.valueOf` caching small ints. Recognizing these helps you reuse proven vocabulary instead of reinventing structure.

code

java · 14 lines
java
// Decorator: stack java.io stream wrappers, each adding one concern
try (var in = new DataInputStream(
                 new BufferedInputStream(
                     new FileInputStream("data.bin")))) {
    int value = in.readInt();   // buffering + typed read, layered
}

// Strategy: pluggable ordering passed to sort (a lambda)
List<User> users = ...;
users.sort(Comparator.comparing(User::name));  // swap algorithm w/o touching sort

// Flyweight gotcha
System.out.println(Integer.valueOf(127) == Integer.valueOf(127)); // true  (cached)
System.out.println(Integer.valueOf(128) == Integer.valueOf(128)); // false (new objects)

go deeper

for a junior

Recognizes a couple of obvious examples (BufferedReader wrapping, Comparator for sorting) even if intent terms are fuzzy.

for a middle

Names several patterns with correct JDK examples and can say which family (creational/structural/behavioural) each is in.

for a senior

Distinguishes look-alike patterns by intent (Decorator vs Proxy vs Adapter), explains the Integer cache gotcha, and knows when not to force a pattern.

for a principal

Uses pattern vocabulary to reason about API design and evolution, and judges when a pattern adds value versus accidental complexity across a codebase.

## What 'GoF patterns' means **Design patterns** are named, reusable solutions to recurring design problems — not code you copy, but a *shape* of collaboration between classes. The canonical catalogue is the 1994 book *Design Patterns* by Gamma, Helm, Johnson, and Vlissides, nicknamed the **Gang of Four (GoF)**. It lists **23 patterns** in three families: - **Creational** (how objects are made): Factory Method, Abstract Factory, Builder, Prototype, Singleton. - **Structural** (how objects are composed): Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy. - **Behavioural** (how objects collaborate/communicate): Strategy, Observer, Command, Iterator, Template Method, State, Chain of Responsibility, Visitor, Mediator, Memento, Interpreter. Knowing the JDK examples matters because patterns are a shared **vocabulary**: saying 'wrap it in a decorator' is faster and clearer than describing the structure, and the JDK already demonstrates each one idiomatically. ## Concrete JDK manifestations **Decorator (structural).** A decorator wraps an object of the same interface to add behaviour transparently. The `java.io` streams are the textbook case: `InputStream` is the component, and `BufferedInputStream`, `DataInputStream`, etc. wrap one to add buffering or typed reads. You stack them: `new DataInputStream(new BufferedInputStream(new FileInputStream(file)))`. Order matters because each layer adds its concern. `Collections.unmodifiableList` and `synchronizedList` are also wrapping decorators. **Strategy (behavioural).** A strategy is a pluggable algorithm passed in. `Comparator` is the JDK's poster child: `list.sort(comparator)` varies the ordering without touching the sort code. Lambdas make supplying a strategy a one-liner: `list.sort(Comparator.comparing(User::name))`. **Factory Method / static factory (creational).** A factory returns an object without the caller using `new` or knowing the concrete class. `Calendar.getInstance()` and `NumberFormat.getInstance()` return a locale-appropriate subtype. `List.of(...)`, `Integer.valueOf(...)` are static factories (Effective Java's Item 1). **Builder (creational).** A builder assembles a complex object step by step via chained calls. `StringBuilder` (`append().append()`), `Stream.Builder`, `Locale.Builder`, `HttpRequest.newBuilder()`. **Command (behavioural).** A command packages a request as an object so it can be queued, logged, or deferred. `Runnable` and `Callable` submitted to an `ExecutorService` are commands the executor consumes. **Iterator (behavioural).** `Iterator`/`Iterable` standardize traversal; the enhanced for-loop is sugar over `Iterable.iterator()`. `ListIterator` adds bidirectional traversal. **Adapter (structural).** Converts one interface to another. `Arrays.asList(array)` adapts an array to a `List` view; `InputStreamReader` adapts a byte `InputStream` to a character `Reader`. **Proxy (structural).** A stand-in that controls access. `java.lang.reflect.Proxy` with an `InvocationHandler` creates dynamic proxies — the basis of many framework interception mechanisms (AOP, lazy loading). **Flyweight (structural).** Shares immutable instances to save memory. `Integer.valueOf` caches boxed integers in the range −128..127, so `Integer.valueOf(100) == Integer.valueOf(100)` is `true` but `Integer.valueOf(1000) == Integer.valueOf(1000)` is `false` — a famous autoboxing gotcha. **Template Method (behavioural).** A base class defines an algorithm skeleton and leaves hook steps to subclasses: `AbstractList` implements list operations in terms of the abstract `get`/`size`. **Observer (behavioural).** Listener mechanisms (Swing listeners, `PropertyChangeListener`) and the reactive `Flow.Publisher`/`Flow.Subscriber` API. Note the legacy `java.util.Observable`/`Observer` is deprecated. ## The takeaway The JDK is a pattern museum. Recognizing a pattern lets you predict an API's structure (e.g. 'this is a builder, so it's immutable until `build()`'), reuse proven names in design discussions, and avoid reinventing solutions. But patterns are descriptive, not prescriptive — don't force a pattern where a plain method or a lambda is simpler (KISS).

  • Why does `Integer.valueOf(127) == Integer.valueOf(127)` return true but `Integer.valueOf(128) == Integer.valueOf(128)` return false?
    `Integer.valueOf` implements the Flyweight pattern: it caches boxed `Integer` instances for −128..127 and returns the shared instance, so `==` (reference equality) is true inside the cache. 128 is outside the cache, so each call makes a new object and `==` is false. Always compare boxed values with `.equals()` or unbox to `int`.
  • Decorator and Proxy have nearly identical structure. How do they differ in intent?
    Both wrap an object implementing the same interface. A Decorator *adds or augments behaviour* (buffering, synchronization). A Proxy *controls access* to the target — lazy loading, access checks, remoting — typically forwarding to the real object unchanged. Same shape, different purpose; intent is what distinguishes the patterns.

saying these in an interview costs you the question

  • Saying patterns are language features — they're design vocabulary, realized with ordinary classes/interfaces.
  • Claiming `java.util.Observable` is the modern Observer — it's deprecated; use listeners or `Flow`.
  • Confusing Decorator (adds behaviour) with Proxy (controls access) or Adapter (changes interface).
  • Asserting `==` works for boxed Integers in general — it only coincidentally works inside the valueOf cache.
  • Forcing a named pattern where a lambda or a single method is simpler (over-engineering).

context