When to Refactor to Patterns — Junior Level¶
Source: Joshua Kerievsky, Refactoring to Patterns (Addison-Wesley, 2004); refactoring.guru/design-patterns
Table of Contents¶
- The one idea to take away
- Two ways to meet a pattern
- A real-world analogy: the desire path
- The trigger is always a smell
- Three directions: to, toward, away
- The smell → pattern table
- A tiny worked example
- Mechanics: the pattern emerges, you don't install it
- Over-engineering vs under-engineering
- How this section differs from the design-patterns section
- Mini Glossary
- Review questions
- Next
The one idea to take away¶
A design pattern is a destination you refactor toward — driven by a problem already living in your code — not a starting point you impose on a blank page.
That single sentence is the whole reason this section exists. When most people learn the Gang of Four (GoF) patterns, they learn them as blueprints: "Here is Strategy, here is its class diagram, now go find a place to use it." That habit produces some of the worst code juniors write — code that is elaborate, abstract, and indirect before it has any reason to be.
Kerievsky's Refactoring to Patterns inverts the order. You don't decide to use a pattern. You write the simplest thing that works, you let the code grow, and when a specific pain appears — duplication, a swelling conditional, a class that changes for five unrelated reasons — then you reach for a pattern as the cure, and you get there in small, safe, behavior-preserving steps.
The discipline in one line: the smell comes first, the pattern comes last, and the steps in between are tiny and reversible.
Two ways to meet a pattern¶
| Design patterns (GoF) approach | Refactoring to patterns approach | |
|---|---|---|
| Starting point | "I'll use Strategy here." | "This method has a switch I keep editing." |
| Driven by | A catalog of solutions | A concrete smell in real code |
| Risk | You add structure you don't need | You add structure you've proven you need |
| When you arrive | Up front, by design | Gradually, by refactoring |
| If you were wrong | Hard to undo — it's load-bearing | Easy to undo — you refactor back |
Both are valuable. The GoF catalog teaches you what the destinations look like. This section teaches you how to know you need one and how to walk there safely. They are complementary, not competing.
A real-world analogy: the desire path¶
Universities used to pour all the concrete walkways first, then plant grass, then watch students cut across the lawn anyway and wear a muddy diagonal line into it — a desire path. Smarter campuses do the opposite: they plant grass everywhere, wait to see where people actually walk, and pave those lines later.
Patterns are the same. If you pave every path you imagine someone might want, you get a maze of empty walkways nobody uses (over-engineering). If you wait until a path is worn into the grass — until the duplication, the repeated edits, the painful conditional is visibly there — you pave exactly the paths that earn their cost.
The worn line in the grass is the smell. The pavement is the pattern.
The trigger is always a smell¶
You should never start a refactoring with "I want to use pattern X." You start with a symptom. The most common triggers:
- Duplication — the same logic copy-pasted, especially duplicated
switch/if-elsechains that branch on the same thing in several places. - Conditional complexity — a method that grows a new
caseevery time a requirement changes. - Divergent change — one class edited for many unrelated reasons. (See Change Preventers.)
- Shotgun surgery — one conceptual change forces edits across many files.
- Primitive obsession / hard-coded structure — a tree of objects faked with nested maps and flags.
If you can't name the smell, you don't yet have a reason to introduce a pattern. "It might be useful later" is not a smell — it's a guess. (That guess has its own name: speculative generality, a dispensable smell.)
Three directions: to, toward, away¶
Most teaching covers only the first direction. All three matter.
1. Refactor to a pattern¶
You go all the way. The smell is bad enough and the pattern fits cleanly, so you finish the journey and arrive at a full Strategy / Decorator / Composite.
2. Refactor toward a pattern¶
You go partway and stop, because partway already removed the pain. Maybe you extracted two subclasses and a common interface — that's halfway to a full Template Method — but the third variation hasn't appeared yet, so you wait. Stopping early is a legitimate, often correct outcome. A half-built pattern that solves today's problem beats a full pattern built on a guess about tomorrow.
3. Refactor away from a pattern¶
A pattern someone added earlier no longer earns its keep. The Strategy has exactly one strategy. The Factory builds exactly one product. The Observer has one observer that never changes. The indirection costs more than it gives. So you remove the pattern and inline it back to something direct.
This third direction is the one almost everyone forgets, and it's a sign of real engineering maturity. Patterns are not trophies; an unused one is just overhead. We cover it in depth in Refactoring Away From Patterns.
Caveat: "away from" is not an excuse to rip out every pattern you don't immediately understand. Remove a pattern only when you can show it carries no variation it was meant to absorb — when it has exactly one of the thing it abstracts, and no concrete plan to grow.
The smell → pattern table¶
This is the heart of the section: a decision table mapping a symptom to a likely destination. Treat it as a hypothesis generator, not a law. Each row links to the pattern's full definition in the design-patterns section.
| Smell you observe | Likely destination | Why |
|---|---|---|
| Duplicated conditional that branches on a type code, repeated in several methods | Strategy, State, or plain polymorphism (Replace Conditional with Polymorphism) | Each branch becomes a class; the dispatch becomes a virtual call |
| Many similar algorithms that differ in one step | Template Method | Hoist the common skeleton; leave the varying step abstract |
| Complex, error-prone object construction (telescoping constructors, half-built objects) | Builder or Factory Method | Move construction logic out of the caller into a dedicated place |
| Conditional logic that adds optional behavior ("if gift-wrapped, if express, if insured…") | Decorator | Stack each optional behavior as a wrapper instead of a flag |
A tree faked with nested maps, flags, or isLeaf checks everywhere | Composite | Treat leaf and branch through one interface; recursion replaces the flags |
instanceof ladder + downcasts that switch on object type | Visitor or polymorphism | Move the per-type behavior onto the types |
A growing if/else selecting one handler from a list | Chain of Responsibility | Each handler decides whether to act or pass along |
| Two interfaces that should talk but don't match | Adapter | Wrap one to speak the other's language |
Read the table as: "If I see this, one of these might be where I'm headed — let me confirm with the mechanics." Never as: "If I see this, install that immediately."
A tiny worked example¶
A duplicated conditional dispatch — the most common road into a pattern.
Before — the smell¶
class Shipping {
BigDecimal cost(Order o) {
switch (o.method()) {
case GROUND: return o.weight().multiply(new BigDecimal("0.50"));
case AIR: return o.weight().multiply(new BigDecimal("2.00"));
case OVERNIGHT: return o.weight().multiply(new BigDecimal("5.00"));
default: throw new IllegalArgumentException();
}
}
int estimatedDays(Order o) {
switch (o.method()) { // the SAME switch, again
case GROUND: return 5;
case AIR: return 2;
case OVERNIGHT: return 1;
default: throw new IllegalArgumentException();
}
}
}
The same switch (o.method()) appears twice. Every new shipping method means editing both methods — and any other method that branches on the same thing. That's the smell: duplicated conditional dispatch. The table points to Strategy / polymorphism.
After — refactored toward Strategy¶
interface ShippingMethod {
BigDecimal cost(BigDecimal weight);
int estimatedDays();
}
class Ground implements ShippingMethod {
public BigDecimal cost(BigDecimal w) { return w.multiply(new BigDecimal("0.50")); }
public int estimatedDays() { return 5; }
}
class Air implements ShippingMethod {
public BigDecimal cost(BigDecimal w) { return w.multiply(new BigDecimal("2.00")); }
public int estimatedDays() { return 2; }
}
class Overnight implements ShippingMethod {
public BigDecimal cost(BigDecimal w) { return w.multiply(new BigDecimal("5.00")); }
public int estimatedDays() { return 1; }
}
Now Shipping just delegates: o.method().cost(o.weight()). A new method is one new class, not edits scattered across every switch. The duplicated dispatch is gone.
When NOT to do this: if there's exactly one
switchonmethod()in the whole codebase and it has three stable branches that haven't changed in two years, leave it. A single, small, stable conditional is clearer as aswitchthan as five files. The pattern earns its keep when the branching is duplicated or churning — not just because branching exists.
Mechanics: the pattern emerges, you don't install it¶
You don't rewrite Shipping into Strategy in one big commit. You get there with a sequence of tiny, behavior-preserving moves, running tests after every one:
- Extract the first branch's body into a method, run tests. (Green.)
- Create the
ShippingMethodinterface and aGroundclass, move the body in, run tests. (Green.) - Point one call site at
Ground, run tests. (Green.) - Repeat for
Air,Overnight. - Delete the now-dead
switch, run tests. (Green.) - Commit.
Each step is reversible. If step 3 breaks a test, you've changed almost nothing and can see exactly what. This is the opposite of "rewrite it over the weekend." Kerievsky's book is fundamentally a catalog of these step sequences — one named sequence per pattern destination. The pattern is the result, not the plan.
This is the lineage of Kent Beck and Martin Fowler:
"Make the change easy, then make the easy change." — refactor to a shape where the new requirement is trivial, then add it.
When NOT to take tiny steps: never. Tiny steps are non-negotiable on code that has tests and matters. The only time you "install in one leap" is throwaway code or a spike you'll delete.
Over-engineering vs under-engineering¶
Patterns live on a spectrum between two failure modes:
UNDER-ENGINEERED <------------------ patterns live here ------------------> OVER-ENGINEERED
copy-paste, the simplest design that absorbs "pattern fever":
one giant switch, the change you actually have Strategy with one strategy,
no abstraction ("just barely enough structure") AbstractFactoryFactory,
layers nobody asked for
- Under-engineering is the smell catalog: duplication, long methods, big conditionals. The cure is adding structure (refactor to a pattern).
- Over-engineering — also called "patterns-happy" or pattern fever — is adding structure nobody needs. Its named smells are speculative generality (built for an imagined future) and needless complexity. The cure is removing structure (refactor away from a pattern, topic 05).
The guiding principle against over-engineering is YAGNI — "You Aren't Gonna Need It." (See YAGNI.) Don't build the flexible thing until the inflexibility actually hurts. Refactoring to patterns is YAGNI's friend: it lets you stay simple now because you trust you can evolve to the pattern later, cheaply, when the need is real.
Caveat in the other direction: YAGNI is not "never abstract." Once you have a real, present smell — duplicated dispatch you keep editing — refusing to extract the pattern is itself a failure (under-engineering). Judgment is knowing which side of the line you're on.
How this section differs from the design-patterns section¶
A common confusion: "Didn't I already learn Strategy in the design-patterns section?" Yes — and these two sections do genuinely different jobs.
| design-patterns section | this section (refactoring-to-patterns) | |
|---|---|---|
| Teaches | What each pattern is — structure, participants, intent | The journey — smell → mechanical steps → pattern |
| Central artifact | The pattern's class diagram | The sequence of tiny refactorings |
| Question answered | "What does Strategy look like?" | "How do I know I need it, and how do I get there safely?" |
| Also teaches | — | The judgment of whether to take the trip at all (and when to turn back) |
So when this section needs to remind you what a Decorator is, it links to the design-patterns section rather than re-explaining it. Your job here is the decision and the route, not the destination's blueprint.
Mini Glossary¶
- Refactor to a pattern — complete the journey from a smell to a full, recognizable pattern.
- Refactor toward a pattern — go partway and stop, because partway already removed the pain.
- Refactor away from a pattern — remove a pattern that no longer earns its indirection.
- Smell — a surface symptom (duplication, big conditional) hinting at a deeper design problem; the trigger for refactoring.
- Behavior-preserving — a change that doesn't alter what the program does, only its internal shape. Tests should stay green.
- Speculative generality — structure added for an imagined future need that never arrives; the core over-engineering smell.
- Pattern fever / patterns-happy — the habit of reaching for a pattern as a first move rather than a last resort.
- YAGNI — "You Aren't Gonna Need It": don't build flexibility until a real need demands it.
- Mechanics — the named, ordered sequence of tiny steps that takes you from smell to pattern.
Review questions¶
-
Is a design pattern a starting point or a destination? A destination you refactor toward, driven by a real smell — not a blueprint you impose on a blank page.
-
What must come before you introduce a pattern? A named smell — duplication, a churning conditional, divergent change. "I want to use pattern X" is not a valid reason.
-
Name the three directions. To a pattern (finish the journey), toward a pattern (stop partway), and away from a pattern (remove one that no longer earns its keep).
-
A
switchon order type appears in five different methods. Where might you be headed? Strategy / State / polymorphism — duplicated conditional dispatch is the classic road to Strategy. Confirm with the mechanics, don't install blindly. -
What is "pattern fever"? Reaching for patterns as a first move, producing structure nobody needs — over-engineering. Its named smell is speculative generality; its antidote is YAGNI.
-
Why take tiny steps instead of rewriting? Each step is behavior-preserving and reversible; tests stay green; if something breaks you've changed almost nothing and can see exactly what.
-
When should you stop toward a pattern? When the partial refactoring already removes the pain and the remaining variation hasn't actually appeared. A half-pattern that solves today's problem beats a full pattern built on a guess.
-
How is this section different from the design-patterns section? That section teaches what a pattern is (its diagram); this section teaches the journey (smell → steps → pattern) and the judgment of whether to take it.
Next¶
- middle.md — when to actually pull the trigger: cost/benefit, the rule of three, partial refactoring, team considerations.
- senior.md — judgment at scale: pattern density, evolutionary architecture, sequencing multiple refactorings.
- professional.md — the economics: the cost of the wrong abstraction, measuring before/after, refactoring under deadline.
- interview.md — interview questions and model answers.
- tasks.md — decide if and which pattern for real snippets.
- find-bug.md — spot patterns applied too early or wrongly.
- optimize.md — propose the right refactoring-to-pattern for smelly code.
In this topic
- junior
- middle
- senior
- professional