Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Design Patterns (GoF and modern)  Module 10 – Anti-patterns and refactoring to patterns

Refactoring toward, and away from, patterns

Refactor toward Strategy and Decorator in small steps, away from a needless Singleton or Visitor, and keep every answer the same at each step.

  • Advanced
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0 and Java 25
  • By MySmartCoPilot
Free intro, Pro: 8 of 30 minutes free
Free: The refactorings in both directions. With a pass: the other 10 sections and 3 interview answers, about 22 minutes.

What you will learn

  • Refactor toward Strategy, State, Decorator, Composite, Observer and Null Object in small steps
  • Refactor away from patterns that no longer pay for themselves, such as a needless Singleton or Visitor
  • Prove with tests that behaviour stays identical at every step

Before you start

What you’ll learn · Before you start

What you will learn

  • Refactor toward Strategy, State, Decorator, Composite, Observer and Null Object in small steps
  • Refactor away from patterns that no longer pay for themselves, such as a needless Singleton or Visitor
  • Prove with tests that behaviour stays identical at every step

Before you start

On this page

Refactoring toward a pattern means moving existing code, one small step at a time and with its behaviour unchanged, until the pattern’s structure appears. Refactoring away from a pattern means taking that structure out again once the variation it served has gone. Joshua Kerievsky’s Refactoring to Patterns treats patterns this way, as destinations that code reaches when it asks for them, and its catalogue of 27 refactorings gives advice for refactoring “to, towards or away from” them. The discipline is the same in both directions: name the variation, keep the tests green after every step, and commit each step so it can be undone alone. Patterns are rarely right on the first day of a design, and rarely right forever.

The refactorings in both directions

Most of the names below are Kerievsky’s; the rest come from Martin Fowler’s catalogue of refactorings or from a language feature:

Toward a pattern

Refactoring What it does
Replace Conditional Logic With Strategy Moves each branch of a conditional into a strategy object that the code using it is given.
Replace State-Altering Conditionals with State Moves the transitions and each status’s behaviour into one class per state.
Move Embellishment To Decorator Moves optional extras out of the core code into wrappers put together once, at set-up.
Replace Implicit Tree With Composite Turns nested lists or strings that form a tree into node objects with one interface.
Replace Hard-Coded Notifications With Observer Replaces direct calls to interested code with listeners registered from outside.
Introduce Null Object Replaces repeated checks for a missing object with an object that does nothing harmful.

Away from a pattern

Refactoring What it does
Inline Singleton Creates the one instance at start-up and passes it to whoever needs it.
Inline Function, for a factory with one product Calls the constructor directly where the factory only ever made one class.
A sealed type and a switch, instead of a Visitor Lets a compiler check every case of a closed hierarchy, without double dispatch.
Remove Middle Man, for a mediator that only forwards Lets callers talk to the real object when the layer between adds no decision.

Refactoring toward a pattern starts from a variation that has become real, and the same steps run backwards when it disappears:

Code moves toward a pattern in small tested steps when its variation becomes real, and back to plain code when it is gone.Plain codea conditional, flags,a direct callSmall steps,tests green after eachA patternStrategy, Decorator,State, Observertoward: the variationbecomes realaway: the variationis gone

Toward a pattern, and away from it

Text description of the diagram

The diagram has three boxes and two directions.

  1. Plain code, such as a conditional, flags or a direct call, leads to a box "Small steps, tests green after each" through an arrow labelled "toward: the variation becomes real".
  2. The small steps lead to a box "A pattern", such as Strategy, Decorator, State or Observer.
  3. A dashed arrow labelled "away: the variation is gone" leads from the pattern back to plain code.

Refactoring away from patterns starts with a map of what depends on them. This scanner reads a small app with Python’s ast module and finds everything that reaches a Singleton, and the factory behind it:

Everything that reaches a Singleton, before it is removed

Python · reach/reach.py

"""Before removing a Singleton, find everything that reaches it, and every factory that only ever makes one class."""
import ast
from pathlib import Path

tree = ast.parse(Path("notify_app.py").read_text())
callers = []
for owner in [None, *[n for n in tree.body if isinstance(n, ast.ClassDef)]]:
    body = tree.body if owner is None else owner.body
    for f in (n for n in body if isinstance(n, ast.FunctionDef)):
        for call in ast.walk(f):
            if isinstance(call, ast.Call) and isinstance(call.func, ast.Attribute) and call.func.attr == "instance":
                callers.append(f"{owner.name}.{f.name}" if owner else f.name)
                break
print(f"Notifications.instance() is called from {len(callers)} places: {', '.join(callers)}")
for cls in (n for n in tree.body if isinstance(n, ast.ClassDef)):
    for f in (n for n in cls.body if isinstance(n, ast.FunctionDef)):
        returns = [r.value for r in ast.walk(f) if isinstance(r, ast.Return) and isinstance(r.value, ast.Call)]
        made = {ast.unparse(r.func) for r in returns}
        if f.name == "create" and len(made) == 1:
            print(f"{cls.name}.create() only ever makes {made.pop()}: a factory with one product")

Output

Notifications.instance() is called from 5 places: welcome, password_reset, order_shipped, Reminders.due, Reminders.overdue
NotifierFactory.create() only ever makes EmailNotifier: a factory with one product

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 reach.py

reach/notify_app.py

"""A small app whose messages go through a Singleton made by a factory with one product."""


class EmailNotifier:
    def send(self, to, text):
        return f"email to {to}: {text}"


class NotifierFactory:
    @staticmethod
    def create():
        return EmailNotifier()


class Notifications:
    _instance = None

    @classmethod
    def instance(cls):
        if cls._instance is None:
            cls._instance = cls()
            cls._instance.notifier = NotifierFactory.create()
        return cls._instance

    def send(self, to, text):
        return self.notifier.send(to, text)


def welcome(user):
    return Notifications.instance().send(user, "welcome aboard")


def password_reset(user):
    return Notifications.instance().send(user, "here is your reset link")


def order_shipped(user, order):
    return Notifications.instance().send(user, f"order {order} has shipped")


class Reminders:
    def due(self, user):
        return Notifications.instance().send(user, "your invoice is due")

    def overdue(self, user):
        return Notifications.instance().send(user, "your invoice is overdue")

Five places call Notifications.instance(), two of them in another class, and the factory only ever makes EmailNotifier. Those five call sites are the work list of an Inline Singleton, and the factory can go in the same series of small steps.

Toward Strategy: a ride fare in four steps Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Toward Decorator: the optional extras of a gift order
  • Away from a Singleton: tests that pass in one order only
  • Away from Visitor: a closed set of shapes
  • Choosing the direction from evidence
  • The first and the last step of four more refactorings
  • At system scale: the Strangler Fig
  • Practise: remove a needless Singleton and its factory
    • Remove a needless Singleton and its one-product factory
  • Check yourself
  • Interview questions
    • What does it mean to refactor toward a pattern, and when would you refactor away from one?
    • A test suite passes on developers' machines but fails in CI about one run in five, always in tests that send notifications through a Singleton. Explain the likely cause, and plan the refactoring away from the Singleton without one big change.
    • You are moving three pricing flags, gift wrap, insurance and express delivery, into decorators. Insurance is a percentage of the total. What can go wrong with the order of the decorators, and how do you prove that the refactoring changed no price?
    • A Java 25 code base has a Visitor over six AST node types with nine visitors. A colleague proposes replacing it with sealed interfaces and switch expressions. What do you weigh, and how would you migrate safely if you agree?
  • Key takeaways

4 runnable examples (JavaScript, MissingCase.java, Python, SealedShapes.java, VisitorShapes.java, step0.mjs, step0.py, step1.mjs, step1.py, step2.mjs, step2.py, step3.mjs, step3.py) · 1 exercise with sample tests · 3 interview answers · about 21 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.