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
| Smell | Pattern to Consider |
|---|---|
| Long if-else chain choosing an algorithm | Strategy |
| Long if-else chain on an object's status | State |
| Same steps copied across classes | Template Method |
| Constructor with many parameters | Builder |
| Repeated null checks | Null Object |
| Subclass explosion from combined options | Decorator or Bridge |
| Tangled calls between many classes | Mediator or Facade |
| Code that creates concrete classes with new everywhere | Factory 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.
