Pattern Thinking
Purpose
Produce the simplest design that survives the change the system will actually see. A pattern is
a named set of consequences, not a named structure — adopting one means accepting its costs
because a force demands them. The two failures this exists to prevent are opposite and equally
common: the design that names a pattern before it has a problem, and the design that reinvents
one badly because the vocabulary was refused.
The output of this reasoning is frequently no pattern. That is a result, not a failure to
find one.
The order of reasoning
Problem what breaks, or is about to, in observable terms
↓
Context where it occurs — layer, lifetime, process, boundary
↓
Forces the concerns that compete: change rate, coupling,
performance, testability, concurrency, compatibility
↓
Variation what varies, along how many axes, and against what
↓
Alternatives relevant options below, direct implementation first
↓
Decision lowest justified lifecycle cost among viable options
↓
Consequences what got worse, written down
Skipping straight to Decision is what "cargo cult" means concretely. A design that cannot
identify a concrete variation, collaboration or boundary has not justified the indirection.
Adapters and facades can earn their place through compatibility or a stable subsystem boundary
without forecasts of new variants.
Java compatibility is a decision constraint: inspect compiler release/toolchains and dependencies.
Records/sealed types are final in Java 17; pattern switch is final in Java 21. Do not introduce
preview features or upgrade a project to fit an example. On older targets, compare supported
language features and ordinary method dispatch. Missing evidence keeps a forecast or performance
benefit conditional, rather than turning it into an assumed requirement.
Workflow
- State the problem with no pattern name in it. "Adding a payment provider touches five
classes and a
switch in each" is a problem. "We need a Strategy" is a conclusion wearing a
problem's clothes. If the first sentence cannot be written, there is nothing to design yet.
- Name the forces and material tension. Patterns can resolve competing forces, but may also
encode a stable collaboration or safety boundary. If the direct implementation already satisfies
the forces with lower lifecycle/debugging cost, keep it.
- Identify axes of variation and evidence. Two present variants are strong evidence, but a
single implementation can still sit behind a justified external, ownership, security or testing
boundary. Price forecast variation explicitly (
java-dry-kiss-yagni).
- Compare relevant alternatives below, starting with the direct implementation. The ladder
is a search aid, not a universal cost ranking: configuration, DI and function values can compose.
Read references/alternatives-ladder.md for the rung
definitions and worked eliminations.
- If a pattern is selected, name its consequences out loud — the indirection, the extra
lifecycle, the dispatch site that moved out of sight, the thing that got harder to read. If
none can be named, the pattern is not yet understood well enough to adopt.
- Deliver a proportionate decision: problem, evidence, chosen mechanism, relevant alternative,
consequence and verification. “No change” needs no catalogue-wide elimination report.
- Re-check the boundary. If the collaboration crosses a process, the local pattern's
guarantees do not travel with it (
gof-patterns-and-distribution).
The alternatives ladder
0 Nothing keep it direct when no force requires indirection
1 Language feature record, sealed interface + exhaustive switch,
enum, generics, Optional, method reference
2 Composition hold a collaborator in a field and delegate
3 Function value Function/Predicate/Supplier/Consumer, or a
single-method domain interface, passed as a lambda
4 Dependency injection the container selects and wires the variant
5 Configuration the variation is data, not code
6 Framework mechanism filter chain, interceptor, event listener,
converter registry, client builder
7 GoF pattern a named structure with named consequences
8 Architectural pattern the problem is a boundary problem, not an
object problem (ports and adapters, CQRS, saga)
Rungs 1–6 are not "avoiding patterns" — several of them are the pattern, expressed through a
mechanism that already exists. A Comparator lambda is Strategy. A servlet filter chain is
Chain of Responsibility. The distinction that matters is design intent versus implementation
mechanism: recognising the intent is what keeps the design legible; hand-building the
classical structure when rungs 1–6 already supply the mechanism is what makes it bloated.
Decision rules
IF the problem statement contains a pattern name
THEN restate it as what breaks, and re-decide from the restatement.
IF one implementation exists and no second is scheduled
THEN do not claim runtime variability. Still retain a structural pattern
when it enforces dependency direction, translates a foreign protocol,
narrows authority or creates an intentional failure-injection seam.
IF variation is one axis and each variant is one behaviour
THEN rung 3 — a function value — before Strategy classes.
IF variation is along two or more independent axes
THEN compare composition to a subclass cross-product. Keep a small hierarchy when
substitutability and shared invariants justify it; do not multiply classes mechanically.
IF the set of variants is closed and you own all of them
THEN compare sealed types/switch against method dispatch, accounting for Java version,
state-owned behavior and type-versus-operation evolution (gof-patterns-in-modern-java).
IF the set of variants is open to code you will never see
THEN define an extension contract and discovery/selection/lifecycle policy.
An injected implementation or registry may suffice; a factory is not mandatory.
IF the "variation" is data — rates, limits, endpoints, flags
THEN configuration. Class-per-value is the commonest false pattern.
IF the pattern is being adopted for performance
THEN state the proposed mechanism and measure against a baseline; a pattern name
proves neither overhead nor speedup, and caching/sharing can avoid work
(java-performance, jmh-microbenchmarks).
IF the collaboration crosses a process boundary
THEN the design problem is failure semantics, not object structure
(gof-patterns-and-distribution).
IF the pattern would exist to make failure or nondeterminism testable
THEN first remove hidden ambient dependencies where possible. A seam over
an external system, clock or nondeterministic source may itself be the
correct production boundary (java-test-design).
What a pattern costs — price these before adopting
- A dispatch site moves out of sight. After Strategy, State, Visitor or Chain of
Responsibility, "what runs here" is answered by wiring rather than by reading. That is the
trade; stable boundaries, testability and ownership can justify it even if wiring rarely changes.
- A lifecycle appears. Creational patterns, Flyweight, Proxy and Singleton each introduce
"who makes this, when, and how many" — and with it the thread-safety and
initialization-order questions. Direct construction also needs ownership and safe publication;
the pattern may centralize an existing responsibility rather than create it.
- The type count rises faster than the behaviour count. Two variants behind an interface is
more declarations to navigate. Count only added complexity, and weigh boundary guarantees;
type ratios are not a quality metric and more variants are not required to justify a seam.
- Stack traces and debugging sessions get longer. Decorator stacks and handler chains are
read at 3 a.m. by someone who did not write them.
- Tests can overuse seams. Mock-only tests can pass while composition is broken.
Keep contract and integration checks where wiring or collaborator semantics matter (
java-test-doubles).
Review checklist
References
- The alternatives ladder — each rung defined by the force
it resolves and the force it fails to resolve, five worked eliminations where a proposed
pattern collapsed into a lower rung, and one where it correctly did not. Read when deciding
whether a pattern is justified.
- Pattern inventory — the 23 patterns in one table: category,
the primary design problem each solves, misuse risk, boundary class, and the skill that owns
it. Read to locate the right skill, or to check that a proposed pattern addresses the problem
actually at hand.
1---2name: gof-pattern-thinking3description: Reasoning from a design problem to a design, where a Gang-of-Four pattern is one possible outcome and "no pattern" is an equally valid one: naming the forces, identifying what varies and along how many axes, walking the alternatives ladder from language feature up to architecture, and pricing the indirection before adopting it. The first of two stages — run this, then gof-pattern-selection maps the result to a shortlist. Use when a pattern name is proposed before the problem is stated, when a review must judge whether an abstraction earns its place, when factories and strategies have accumulated that trace to no requirement, when an indirection needs pricing, or when a design is starting and the vocabulary is about to be chosen by habit. Does not cover the individual patterns (the gof-* skills), telling lookalike patterns apart (gof-pattern-confusion), the misuse catalogue (gof-pattern-antipatterns), enterprise/PoEAA patterns (pattern-selection-and-composition), or SOLID as a framing (java-solid).4---56# Pattern Thinking78## Purpose910Produce the simplest design that survives the change the system will actually see. A pattern is11a named set of _consequences_, not a named structure — adopting one means accepting its costs12because a force demands them. The two failures this exists to prevent are opposite and equally13common: the design that names a pattern before it has a problem, and the design that reinvents14one badly because the vocabulary was refused.1516The output of this reasoning is frequently **no pattern**. That is a result, not a failure to17find one.1819## The order of reasoning2021```text22Problem what breaks, or is about to, in observable terms23 ↓24Context where it occurs — layer, lifetime, process, boundary25 ↓26Forces the concerns that compete: change rate, coupling,27 performance, testability, concurrency, compatibility28 ↓29Variation what varies, along how many axes, and against what30 ↓31Alternatives relevant options below, direct implementation first32 ↓33Decision lowest justified lifecycle cost among viable options34 ↓35Consequences what got worse, written down36```3738Skipping straight to Decision is what "cargo cult" means concretely. A design that cannot39identify a concrete variation, collaboration or boundary has not justified the indirection.40Adapters and facades can earn their place through compatibility or a stable subsystem boundary41without forecasts of new variants.4243Java compatibility is a decision constraint: inspect compiler release/toolchains and dependencies.44Records/sealed types are final in Java 17; pattern switch is final in Java 21. Do not introduce45preview features or upgrade a project to fit an example. On older targets, compare supported46language features and ordinary method dispatch. Missing evidence keeps a forecast or performance47benefit conditional, rather than turning it into an assumed requirement.4849## Workflow50511. **State the problem with no pattern name in it.** "Adding a payment provider touches five52 classes and a `switch` in each" is a problem. "We need a Strategy" is a conclusion wearing a53 problem's clothes. If the first sentence cannot be written, there is nothing to design yet.542. **Name the forces and material tension.** Patterns can resolve competing forces, but may also55 encode a stable collaboration or safety boundary. If the direct implementation already satisfies56 the forces with lower lifecycle/debugging cost, keep it.573. **Identify axes of variation and evidence.** Two present variants are strong evidence, but a58 single implementation can still sit behind a justified external, ownership, security or testing59 boundary. Price forecast variation explicitly (`java-dry-kiss-yagni`).604. **Compare relevant alternatives** below, starting with the direct implementation. The ladder61 is a search aid, not a universal cost ranking: configuration, DI and function values can compose.62 Read [references/alternatives-ladder.md](references/alternatives-ladder.md) for the rung63 definitions and worked eliminations.645. **If a pattern is selected, name its consequences out loud** — the indirection, the extra65 lifecycle, the dispatch site that moved out of sight, the thing that got harder to read. If66 none can be named, the pattern is not yet understood well enough to adopt.676. **Deliver a proportionate decision:** problem, evidence, chosen mechanism, relevant alternative,68 consequence and verification. “No change” needs no catalogue-wide elimination report.697. **Re-check the boundary.** If the collaboration crosses a process, the local pattern's70 guarantees do not travel with it (`gof-patterns-and-distribution`).7172## The alternatives ladder7374```text750 Nothing keep it direct when no force requires indirection761 Language feature record, sealed interface + exhaustive switch,77 enum, generics, Optional, method reference782 Composition hold a collaborator in a field and delegate793 Function value Function/Predicate/Supplier/Consumer, or a80 single-method domain interface, passed as a lambda814 Dependency injection the container selects and wires the variant825 Configuration the variation is data, not code836 Framework mechanism filter chain, interceptor, event listener,84 converter registry, client builder857 GoF pattern a named structure with named consequences868 Architectural pattern the problem is a boundary problem, not an87 object problem (ports and adapters, CQRS, saga)88```8990Rungs 1–6 are not "avoiding patterns" — several of them _are_ the pattern, expressed through a91mechanism that already exists. A `Comparator` lambda is Strategy. A servlet filter chain is92Chain of Responsibility. The distinction that matters is **design intent versus implementation93mechanism**: recognising the intent is what keeps the design legible; hand-building the94classical structure when rungs 1–6 already supply the mechanism is what makes it bloated.9596## Decision rules9798```text99IF the problem statement contains a pattern name100THEN restate it as what breaks, and re-decide from the restatement.101102IF one implementation exists and no second is scheduled103THEN do not claim runtime variability. Still retain a structural pattern104 when it enforces dependency direction, translates a foreign protocol,105 narrows authority or creates an intentional failure-injection seam.106107IF variation is one axis and each variant is one behaviour108THEN rung 3 — a function value — before Strategy classes.109110IF variation is along two or more independent axes111THEN compare composition to a subclass cross-product. Keep a small hierarchy when112 substitutability and shared invariants justify it; do not multiply classes mechanically.113114IF the set of variants is closed and you own all of them115THEN compare sealed types/switch against method dispatch, accounting for Java version,116 state-owned behavior and type-versus-operation evolution (gof-patterns-in-modern-java).117118IF the set of variants is open to code you will never see119THEN define an extension contract and discovery/selection/lifecycle policy.120 An injected implementation or registry may suffice; a factory is not mandatory.121122IF the "variation" is data — rates, limits, endpoints, flags123THEN configuration. Class-per-value is the commonest false pattern.124125IF the pattern is being adopted for performance126THEN state the proposed mechanism and measure against a baseline; a pattern name127 proves neither overhead nor speedup, and caching/sharing can avoid work128 (java-performance, jmh-microbenchmarks).129130IF the collaboration crosses a process boundary131THEN the design problem is failure semantics, not object structure132 (gof-patterns-and-distribution).133134IF the pattern would exist to make failure or nondeterminism testable135THEN first remove hidden ambient dependencies where possible. A seam over136 an external system, clock or nondeterministic source may itself be the137 correct production boundary (java-test-design).138```139140## What a pattern costs — price these before adopting141142- **A dispatch site moves out of sight.** After Strategy, State, Visitor or Chain of143 Responsibility, "what runs here" is answered by wiring rather than by reading. That is the144 trade; stable boundaries, testability and ownership can justify it even if wiring rarely changes.145- **A lifecycle appears.** Creational patterns, Flyweight, Proxy and Singleton each introduce146 "who makes this, when, and how many" — and with it the thread-safety and147 initialization-order questions. Direct construction also needs ownership and safe publication;148 the pattern may centralize an existing responsibility rather than create it.149- **The type count rises faster than the behaviour count.** Two variants behind an interface is150 more declarations to navigate. Count only added complexity, and weigh boundary guarantees;151 type ratios are not a quality metric and more variants are not required to justify a seam.152- **Stack traces and debugging sessions get longer.** Decorator stacks and handler chains are153 read at 3 a.m. by someone who did not write them.154- **Tests can overuse seams.** Mock-only tests can pass while composition is broken.155 Keep contract and integration checks where wiring or collaborator semantics matter (`java-test-doubles`).156157## Review checklist158159- [ ] The problem is stated in observable terms, with no pattern name in it160- [ ] The forces and material tension/boundary are named161- [ ] What varies is identified, with its axes and today's cardinality162- [ ] Relevant simpler alternatives were compared; mechanisms can combine without visiting every rung163- [ ] The pattern's consequences are written down, including what got worse164- [ ] Every one-implementation interface has a concrete boundary, authority or testability reason165- [ ] Data-only variation uses validated configuration when appropriate; behavior and invariants stay explicit166- [ ] Any performance claim rests on a measurement, not on structure167- [ ] If the collaboration crosses a process, failure semantics are designed, not inherited168169## References170171- [The alternatives ladder](references/alternatives-ladder.md) — each rung defined by the force172 it resolves and the force it fails to resolve, five worked eliminations where a proposed173 pattern collapsed into a lower rung, and one where it correctly did not. Read when deciding174 whether a pattern is justified.175- [Pattern inventory](references/pattern-inventory.md) — the 23 patterns in one table: category,176 the primary design problem each solves, misuse risk, boundary class, and the skill that owns177 it. Read to locate the right skill, or to check that a proposed pattern addresses the problem178 actually at hand.