Skip to content
FollowtheDamnCode

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

TechniqueMoveCures
Move MethodRelocate a method to the class it uses mostFeature Envy
Move FieldRelocate a field to the class that uses it mostShotgun Surgery, Inappropriate Intimacy
Extract ClassSplit out a group of fields and methods into a new classLarge Class, Divergent Change
Inline ClassFold a class that does too little into its userLazy Class
Hide DelegateAdd a method so callers don't reach through an objectMessage Chains
Remove Middle ManLet callers use the delegate directlyMiddle Man
Introduce Foreign Method / Local ExtensionAdd missing behaviour for a class you can't edit, in one placeIncomplete 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).

Extract Class: a person's phone details become their own class

In code

Extract Class and Move Method, same output
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: True

Run 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

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.