100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Software Design

Refactoring to Patterns

How to evolve messy, ad-hoc code toward well-known design patterns incrementally, using the smell-driven refactoring approach popularized by Joshua Kerievsky.

PracticeAdvanced11 min readJul 10, 2026
Analogies

Patterns as a Refactoring Destination, Not a Starting Point

'Refactoring to patterns' inverts the usual way patterns are taught: instead of picking a pattern up front and designing around it, you let code smells drive small refactorings, and a recognizable pattern emerges as a byproduct once enough smells are resolved. This matters because premature pattern application — imposing a Strategy or Visitor before the code has demonstrated the variation that justifies it — adds indirection with no payoff, which is itself a form of over-engineering. The discipline is to keep the simplest thing that works until a specific smell (duplicated conditional logic, a switch statement that keeps growing, a class instantiating concrete types directly) signals that a pattern will pay for its complexity.

🏏

Cricket analogy: A young batter shouldn't be taught the reverse-scoop shot on day one; the shot 'emerges' as a refactoring only after the coach sees a specific recurring problem — being cramped by yorkers at the death — that the reverse-scoop specifically solves, not as a starting template.

From Switch Statement to Strategy: A Worked Smell

The most common refactoring-to-patterns trigger is a switch statement (or long if/else chain) on a type code that keeps growing every time a new variant is added, and that same switch statement is duplicated in two or three other methods across the codebase. The refactoring path is: first apply Extract Method to isolate each branch's logic into its own named method, then apply Replace Conditional with Polymorphism (or, in languages without inheritance-friendly types, Replace Type Code with Strategy objects) so each variant becomes its own class implementing a shared interface, and the switch collapses into a single polymorphic call or a lookup in a registry map. The payoff is that adding a new variant becomes 'add one new class,' not 'find and update every switch statement across the codebase.'

🏏

Cricket analogy: A switch statement that keeps growing is like an umpire's mental checklist for every possible dismissal type, hardcoded in his head; refactoring to Strategy is like each dismissal type (LBW, caught, run-out) becoming its own specialist review protocol that DRS applies polymorphically rather than the umpire re-deriving rules from scratch each time.

python
# BEFORE: growing switch/if-elif duplicated in shipping and pricing modules
def calculate_shipping_cost(order):
    if order.method == "standard":
        return order.weight * 0.5
    elif order.method == "express":
        return order.weight * 1.5 + 5
    elif order.method == "overnight":
        return order.weight * 3.0 + 15
    # every new method requires editing this AND estimate_delivery_days()

# AFTER: Replace Conditional with Polymorphism (Strategy)
class ShippingMethod:
    def cost(self, order): raise NotImplementedError
    def delivery_days(self, order): raise NotImplementedError

class Standard(ShippingMethod):
    def cost(self, order): return order.weight * 0.5
    def delivery_days(self, order): return 5

class Express(ShippingMethod):
    def cost(self, order): return order.weight * 1.5 + 5
    def delivery_days(self, order): return 2

class Overnight(ShippingMethod):
    def cost(self, order): return order.weight * 3.0 + 15
    def delivery_days(self, order): return 1

SHIPPING_METHODS = {"standard": Standard(), "express": Express(), "overnight": Overnight()}

def calculate_shipping_cost(order):
    return SHIPPING_METHODS[order.method].cost(order)
# Adding "international" now means one new class, not two edited functions

Kerievsky's rule of thumb: 'refactor to a pattern the third time a smell shows up, not the first.' Two occurrences might be coincidence; a third occurrence of the same duplicated conditional is strong evidence the variation is a real, recurring axis worth abstracting.

Keeping Refactorings Small and Reversible

The safest way to refactor toward a pattern is a sequence of tiny, individually compilable, individually testable steps — never a single large leap from 'switch statement' to 'finished Strategy hierarchy' in one commit. A typical sequence is: extract the first branch's body into a private method with a descriptive name, repeat for each branch, introduce an interface that all the extracted methods' signatures share, wrap each branch in a class implementing that interface, replace the conditional with a map lookup or polymorphic dispatch, and only then delete the original conditional. At every step the test suite should stay green, which means each step can be committed independently and reverted independently if it turns out to be wrong.

🏏

Cricket analogy: A team rebuilding its batting order doesn't swap five positions in one match; they test one change (moving the opener to three) for a full series, confirm it works, then make the next single change — exactly like committing one refactoring step at a time.

Beware 'pattern-itis' during a refactor: once you start seeing Strategy everywhere, it's tempting to abstract every remaining conditional preemptively. Stop refactoring once the actual smell is resolved — don't extend the pattern to hypothetical future variants that haven't appeared yet (YAGNI).

  • Refactoring to patterns lets a pattern emerge from smells rather than being imposed upfront.
  • Premature pattern application adds indirection without payoff and is itself a form of over-engineering.
  • A growing, duplicated switch statement on a type code is the classic trigger for Replace Conditional with Polymorphism.
  • Kerievsky's rule of thumb: refactor to a pattern on the third occurrence of a smell, not the first.
  • Refactor in small, individually testable, individually revertible steps — never one large leap.
  • Keep the test suite green after every single small step so regressions are traceable to one change.
  • Stop once the actual smell is resolved; avoid extending patterns to hypothetical future variants (YAGNI).

Practice what you learned

Was this page helpful?

Topics covered

#SoftwareDesign#DesignPatternsStudyNotes#SoftwareEngineering#RefactoringToPatterns#Refactoring#Patterns#Destination#Not#StudyNotes#SkillVeris