Refactoring: Moving Features
Move methods and fields to the class that owns the data, split classes that do too much, and decide how much to hide behind a delegate.
Big idea
Once functions are well named, the next question is where they live. Moving features means putting behaviour next to the data it uses, splitting classes with more than one job, and folding together classes that barely justify themselves — the main tools against coupling smells.
The techniques
| Technique | Move | Cures |
|---|---|---|
| Move Method | Relocate a method to the class it uses most | Feature Envy |
| Move Field | Relocate a field to the class that uses it most | Shotgun Surgery, Inappropriate Intimacy |
| Extract Class | Split out a group of fields and methods into a new class | Large Class, Divergent Change |
| Inline Class | Fold a class that does too little into its user | Lazy Class |
| Hide Delegate | Add a method so callers don't reach through an object | Message Chains |
| Remove Middle Man | Let callers use the delegate directly | Middle Man |
| Introduce Foreign Method / Local Extension | Add missing behaviour for a class you can't edit, in one place | Incomplete Library Class |
Think of it like
Reorganising a kitchen. The pan lived in the hallway cupboard because there was space; you walk to it every evening. Move it next to the stove (Move Method). The drawer holding cutlery, batteries, and string becomes two drawers (Extract Class). The tiny box that only ever held one spoon goes (Inline Class).
In code
class EmployeeBefore:
def __init__(self, name, area, number, salary):
self.name, self.area, self.number, self.salary = name, area, number, salary
def phone(self):
return f"({self.area}) {self.number[:3]}-{self.number[3:]}"
def card(self):
return f"{self.name} {self.phone()}"
class Phone: # Extract Class: phone fields + behaviour
def __init__(self, area, number):
self.area, self.number = area, number
def formatted(self): # Move Method: the logic follows its data
return f"({self.area}) {self.number[:3]}-{self.number[3:]}"
class Employee:
def __init__(self, name, phone, salary):
self.name, self.office_phone, self.salary = name, phone, salary
def phone(self): # Hide Delegate: callers needn't know Phone
return self.office_phone.formatted()
def card(self):
return f"{self.name} {self.phone()}"
old = EmployeeBefore("Mira", "020", "5550192", 50_000)
new = Employee("Mira", Phone("020", "5550192"), 50_000)
print(new.card())
print("same as before:", old.card() == new.card())output
Mira (020) 555-0192
same as before: TrueRun and edit it in the app.
Phone can now be reused for a mobile number or a supplier, and phone formatting changes in one place.
A method in Invoice reads seven fields of Customer and none of Invoice. Where should it go?
Callers write order.customer.address.city everywhere. Which refactoring reduces how much they know?
Watch it
How it relates
- These are the fixes for Couplers and Change Preventers.
- Extract Class is Single Responsibility in action; Hide Delegate applies the Law of Demeter from Coupling and Cohesion.
- Facade is Hide Delegate at the scale of a subsystem.
Key terms
- Move Method — Relocating a method to the class whose data it mainly uses.
- Extract Class — Splitting part of a class's responsibilities into a new class.
- Inline Class — Merging a class that does too little into another.
- Hide Delegate — Adding a method so clients don't navigate through an object to its collaborator.