skip to content

Refactoring to Patterns

Patterns usually land better when you refactor into them because a smell demanded it, rather than designing them in up front. You will cover Kerievsky's moves in both directions, including refactoring away from a pattern that was applied too eagerly.

part ofSoftware design & architectureoverview, primer and where to startread it →
on this pageshow

questions

6

What does "refactoring to patterns" mean, and why is introducing a design pattern through refactoring often preferred over choosing patterns up front?

level: juniorimportance: must knowfreq 58%

answer

  1. smell → refactoring steps → pattern
  2. pattern is destination, not starting point
  3. Kerievsky: toward / further toward / away
  4. avoid speculative generality
  5. needs tests to preserve behavior

basics

~20 s

It means you don't pick patterns before writing code. You write the simplest thing that works, and when the code starts to smell (duplication, a growing conditional), you refactor in small behavior-preserving steps until a known pattern emerges. The smell justifies the pattern.

solid answer

~50 s

Named after Joshua Kerievsky's book, refactoring to patterns is the practice of letting a design pattern be the destination of a refactoring rather than an up-front design decision. You start with the simplest design that passes the tests. When real duplication, a growing conditional, or a rigid dependency appears, you name the smell and apply a sequence of small behavior-preserving refactorings whose end state happens to be a known pattern (Strategy, State, Decorator, Template Method, Factory). The benefit is evidence: the pattern is added only where the code has already proved it needs the flexibility, so you avoid speculative generality and the indirection, extra types, and cognitive load a pattern costs. It also lowers risk, since each step is small and test-covered rather than a big-bang redesign. Kerievsky frames three directions: refactoring toward a pattern, further toward it, and away from one that no longer earns its keep.

go deeper

for a junior

Define refactoring (structure changes, behavior preserved, tests green) and say patterns should emerge from smells like duplication or a growing conditional, not be chosen up front.

for a middle

Name concrete smell→pattern pairs and the small steps used to get there (Extract Function/Class, Extract Interface), and mention that you can stop partway.

for a senior

Frame the trade-off explicitly: cost of speculative generality vs. cost of delaying, which decisions are reversible, and the need for characterization tests in legacy code.

for a principal

Talk about it as an organizational stance — evolutionary design with a strong test/CI backbone, which decisions are genuinely one-way doors, and how to keep teams from both pattern-happiness and abstraction paralysis.

## Terms first **Refactoring** = changing the internal structure of code *without changing its externally observable behavior*, in small steps, with tests green before and after each step. Extract Function, Rename, Pull Up Method, Extract Interface are typical steps. **Design pattern** = a named, recurring solution to a recurring design problem, with a known structure, known consequences, and a name the team can say out loud ("this is a Strategy"). **Code smell** = a surface symptom in code that usually indicates a deeper design problem: duplicated code, a long method, a switch/if-chain on a type code, a class that changes for many reasons, a flag parameter that toggles behavior. **Refactoring to patterns** = combining the three: a smell is the *evidence*, a sequence of refactorings is the *mechanism*, a pattern is the *destination*. ## The problem it solves The classic failure mode after learning the GoF catalog is *pattern-happy design*: choosing Abstract Factory, Visitor, and a plugin registry on day one "because we'll need it". That is **speculative generality** — flexibility paid for now against a requirement that may never arrive. The costs are real and immediate: - more types and files to navigate; behavior scattered across indirection layers; - harder debugging (the call you want is behind two virtual dispatches); - the abstraction is usually guessed at the wrong seam, because you had one example, and one example never reveals the axis of variation; - it is *harder to remove* a wrong abstraction than to add a right one later. The opposite failure exists too: never introducing patterns, so a 400-line conditional keeps growing. Refactoring to patterns is the middle path — **evolutionary design**: simplest thing now, restructure when the code tells you. ## How the practice actually runs 1. **Notice the smell.** "This switch on `type` has appeared in three methods." "Every subclass repeats the same five steps in the same order." 2. **Name the target pattern**, but hold it loosely — it is a hypothesis about where the code wants to go. 3. **Take small steps**, each independently green: Extract Function, Extract Class, Move Function, Extract Interface, Replace Constructor with Factory Method, Pull Up / Push Down. 4. **Stop when the pain stops.** Kerievsky's point is you may refactor only *partway* toward a pattern and that is fine — a couple of extracted classes may remove the smell without the full catalog structure. ## Kerievsky's three directions - **Toward a pattern** — the smell says the pattern's forces are present (e.g. Replace Conditional with Strategy). - **Further toward a pattern** — a partial structure exists and more variation arrives, so you complete it. - **Away from a pattern** — the pattern was over-applied or the requirement disappeared, so you inline it back (e.g. Inline Singleton, collapse a one-implementation Strategy). That third direction is what separates this practice from "pattern advocacy": patterns are not a one-way ratchet. ## Preconditions and edge cases - **You need tests.** Behavior preservation is only a claim without them; with legacy code, write characterization tests first. - **Not everything should be deferred.** Cross-cutting, hard-to-reverse decisions — persistence boundaries, public API shape, security boundaries, module topology — deserve deliberate up-front design, because refactoring them later is not a local change. Refactoring to patterns is strongest for *internal* class-level design. - **Refactoring ≠ rewriting.** If you delete and re-implement, you lost the safety property that makes the practice cheap. ## The short version to say in an interview "Patterns are a destination, not a starting point. I let a smell justify the pattern, get there in small green steps, and I'm equally willing to refactor back out when the pattern stops paying for itself."

  • Are there design decisions you would NOT defer to a later refactoring?
    Yes — decisions that are expensive or impossible to reverse locally: module and service boundaries, the persistence and transaction model, public API/wire contracts, security and authorization boundaries, and data migration strategy. Those deserve deliberate up-front design; refactoring to patterns targets internal, class-level structure where a change stays inside one codebase and can be made in green steps.
  • What do you need in place before you start refactoring toward a pattern in legacy code?
    A test harness around the behavior you're about to move. If unit tests don't exist, write characterization tests (tests that pin current behavior, whatever it is), often via a seam or an approval/golden-master test. Without them you can't claim behavior preservation, and the refactoring becomes a rewrite with unknown risk.

You don't pave a walkway across a lawn on day one and hope people use it. You wait for the desire path worn by actual foot traffic, then pave that. The pattern is the pavement; the smell is the worn grass.

saying these in an interview costs you the question

  • "Good architects choose the right patterns before coding" — treats patterns as up-front deliverables and produces speculative generality.
  • "More patterns means better design" — patterns have costs (indirection, types, cognitive load) and must be earned.
  • "Refactoring to a pattern means rewriting the class" — it's a sequence of small behavior-preserving steps, each test-green.
  • "Once you introduce a pattern you shouldn't remove it" — refactoring away from patterns is an explicit, equally valid direction.
  • Confusing refactoring with adding features; a refactoring changes structure only, never observable behavior.

context

open as a page

Which code smells motivate the refactorings "Replace Conditional Logic with Strategy" and "Replace State-Altering Conditionals with State", and how do you decide which of the two patterns the code actually wants?

level: middleimportance: must knowfreq 62%

basics

~20 s

Both start from big if/switch chains. Use Strategy when the branches are interchangeable ways to compute the same thing chosen by the caller or config. Use State when the branches depend on an object's current mode and the branches also decide the next mode.

open as a page

What smell does the refactoring "Form Template Method" address, what are its mechanical steps, and what are its main drawbacks?

level: middleimportance: should knowfreq 38%

basics

~20 s

It targets sibling subclasses whose methods do the same steps in the same order but differ in a few step bodies. You make the steps identical in shape, pull the shared sequence up into the superclass as one method, and leave the differing steps as overridable hooks.

open as a page

When does the refactoring "Move Embellishment to Decorator" apply, how do you perform it, and what are its limits compared with adding another subclass or another conditional?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Apply it when a class's core job is buried under optional extras controlled by flags — logging, caching, compression, discounts. You extract each extra into a wrapper class that implements the same interface, delegates to the wrapped object, and adds its bit. Callers compose only the extras they need.

open as a page

What does "refactoring away from a pattern" mean, what signals tell you a pattern is over-applied, and how would you safely remove one from a live codebase?

level: seniorimportance: should knowfreq 30%

basics

~20 s

It means removing a pattern that costs more than it gives — for example a Strategy interface with one implementation or an unnecessary Singleton. You inline the indirection step by step, with tests green, until the simpler direct code remains.

open as a page

As a technical leader, how do you decide when a design should be reached through incremental refactoring to patterns versus committed to up front, and how do you keep a team from either pattern-happiness or abstraction paralysis?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

Decide by reversibility and blast radius. Decisions you can change later inside one codebase (class structure) should emerge from refactoring. Decisions that are expensive to undo — data models, public contracts, service boundaries, security — deserve up-front design. Teams need tests and a norm that abstractions must be earned and can be removed.

open as a page