Design Patterns Refactoring to Patterns

Refactoring improves the structure of working code without changing what it does. Many teams reach patterns through refactoring instead of planning them upfront. Smelly code reveals the need, and a pattern offers the cleanup.

The Layman's View

A closet starts tidy and slowly fills with clutter. Nobody plans the mess. One weekend, the owner sorts items into boxes, adds shelves, and labels sections. Refactoring to patterns follows the same story: working code gets reorganized into a cleaner shape.

The Safe Refactoring Loop

   ┌──────────────────────────────┐
   │ 1. Write or confirm tests    │
   └──────────────┬───────────────┘
                  ▼
   ┌──────────────────────────────┐
   │ 2. Make one small change     │
   └──────────────┬───────────────┘
                  ▼
   ┌──────────────────────────────┐
   │ 3. Run the tests             │
   └──────────────┬───────────────┘
        pass ◀────┴────▶ fail
         │                │
         ▼                ▼
   Commit and repeat   Undo the change

Tests act as a safety net. Small steps keep mistakes cheap to find.

Code Smells and Matching Patterns

SmellPattern to Consider
Long if-else chain choosing an algorithmStrategy
Long if-else chain on an object's statusState
Same steps copied across classesTemplate Method
Constructor with many parametersBuilder
Repeated null checksNull Object
Subclass explosion from combined optionsDecorator or Bridge
Tangled calls between many classesMediator or Facade
Code that creates concrete classes with new everywhereFactory Method

Worked Example: If-Else to Strategy

Before

double shippingCost(String method, double weight) {
    if (method.equals("STANDARD")) {
        return weight * 1.0;
    } else if (method.equals("EXPRESS")) {
        return weight * 2.5 + 5;
    } else if (method.equals("PICKUP")) {
        return 0;
    }
    throw new IllegalArgumentException();
}

Every new shipping method forces an edit inside this function. Tests must cover every branch each time.

Step-by-Step Moves

1. Extract each branch body into its own small method
2. Create a ShippingStrategy interface with one method
3. Move each method into a class that implements the interface
4. Replace the if-else chain with a lookup of the right strategy
5. Run tests after every move

After

interface ShippingStrategy { double cost(double weight); }

class StandardShipping implements ShippingStrategy {
    public double cost(double weight) { return weight * 1.0; }
}

class ExpressShipping implements ShippingStrategy {
    public double cost(double weight) { return weight * 2.5 + 5; }
}

class PickupShipping implements ShippingStrategy {
    public double cost(double weight) { return 0; }
}

class ShippingCalculator {
    private Map strategies = new HashMap();
    ShippingCalculator() {
        strategies.put("STANDARD", new StandardShipping());
        strategies.put("EXPRESS",  new ExpressShipping());
        strategies.put("PICKUP",   new PickupShipping());
    }
    double shippingCost(String method, double weight) {
        return strategies.get(method).cost(weight);
    }
}

A new method such as OVERNIGHT needs one new class and one map entry. Existing code stays untouched.

Before and After Shape

BEFORE                           AFTER
┌─────────────────────┐          ┌───────────────┐   ┌──────────────────┐
│ shippingCost()      │          │ Calculator    │──▶│ ShippingStrategy │
│  if / else if /...  │          └───────────────┘   └────────▲─────────┘
└─────────────────────┘                          Standard Express Pickup

When Not to Refactor

  • Code with no tests and a tight deadline needs tests first.
  • Rarely touched, stable code often deserves peace.
  • Two-branch conditions rarely justify a new class hierarchy.

Apply the "rule of three": tolerate duplication twice, and refactor when it appears the third time.

Step-by-Step Walkthrough

Follow the shipping example. Extract each branch of the if-else chain into its own small method and run the tests. Introduce the ShippingStrategy interface and move one method at a time into a class that implements it. After every move, run the tests again.

Replace the if-else chain with a map lookup once all three classes exist. Delete the old methods when the tests pass. The behavior stays identical, and the structure becomes open to new shipping methods.

Safety Habits

  • Commit after every green test run to keep every step reversible.
  • Avoid mixing refactoring with new features in the same change.
  • Ask a teammate to review structural changes.

Practice Idea

Find a method in your own code with three or more branches. Write tests for each branch and try the same extraction steps.

Plain-Words Summary

Refactoring to patterns turns design theory into daily craft. Nobody needs to guess the perfect architecture on day one. Write clear code, notice repeated pain, and apply the pattern that removes it. Patience and tests make the journey safe. Over months, small improvements add up to a codebase that welcomes change instead of resisting it.

Key Takeaways

  • Refactoring changes structure and keeps behavior identical.
  • Code smells point to patterns that fit the problem.
  • Small steps and frequent test runs keep the work safe.
  • Many good designs emerge from cleanup instead of upfront planning.