| name | refactoring-moving-features |
| description | Techniques for moving behavior and data between classes: Move Method, Move Field, Extract Class, Inline Class, Hide Delegate, Remove Middle Man. All with Python before/after examples. |
Moving Features Between Objects
Objects should have the right responsibilities. When behavior or data is in the wrong place, these refactorings move it to where it belongs.
When to Use This Skill
Triggers:
- A method uses more data from another class than its own (Feature Envy)
- A class has too many responsibilities (Large Class)
- Changing one class requires changing many others (Shotgun Surgery)
- Two classes are too tightly coupled (Inappropriate Intimacy)
- Client code chains through multiple objects (Message Chains)
- A class mostly delegates to another (Middle Man)
Don't Triggers:
- The problem is within a single method's internal structure (use
refactoring-composing-methods)
- The problem is about complex conditional logic (use
refactoring-simplifying-conditionals)
- You're trying to identify what's wrong (use
refactoring-code-smells)
Move Method
Problem: A method uses or is used by more features of another class than its own.
Solution: Create a method in the class it uses most, and delegate from the original.
class Account:
def __init__(self, account_type, days_overdrawn):
self.type = account_type
self.days_overdrawn = days_overdrawn
def calculate_interest(self):
if self.days_overdrawn > 0:
return self.type.get_overdraft_interest_rate() * self.days_overdrawn
return self.type.get_normal_interest_rate()
class AccountType:
def __init__(self, is_premium):
self.is_premium = is_premium
def get_overdraft_interest_rate(self):
return 0.1 if self.is_premium else 0.15
def get_normal_interest_rate(self):
return 0.03 if self.is_premium else 0.05
class Account:
def __init__(self, account_type, days_overdrawn):
self.type = account_type
self.days_overdrawn = days_overdrawn
def calculate_interest(self):
if self.days_overdrawn > 0:
return self._overdraft_interest_rate() * self.days_overdrawn
return self._normal_interest_rate()
def _overdraft_interest_rate(self):
return 0.1 if self.type.is_premium else 0.15
def _normal_interest_rate(self):
return 0.03 if self.type.is_premium else 0.05
Steps:
- Examine all features used by the method in its current class.
- Examine the target class to see if it already has a similar method.
- Create a new method in the target class. Copy the body.
- Turn the original method into a delegation, or remove it if no other callers exist.
Move Field
Problem: A field is used by another class more than the class it is defined in.
Solution: Create a field in the new class and redirect all users.
class Account:
def __init__(self, discount_rate):
self.discount_rate = discount_rate
class Customer:
def __init__(self, account):
self.account = account
def apply_discount(self, amount):
return amount * (1 - self.account.discount_rate)
class Account:
def __init__(self):
pass
class Customer:
def __init__(self, discount_rate):
self.discount_rate = discount_rate
def apply_discount(self, amount):
return amount * (1 - self.discount_rate)
Steps:
- Make sure the source field is encapsulated (use getter/setter or property).
- Create a field and accessors in the target class.
- Change all callers to use the new field.
- Remove the field from the source class.
Extract Class
Problem: One class does the work of two.
Solution: Create a new class and move the relevant fields and methods.
class Person:
def __init__(self, name, office_area_code, office_number):
self.name = name
self.office_area_code = office_area_code
self.office_number = office_number
def get_telephone_number(self):
return f"({self.office_area_code}) {self.office_number}"
def get_office_area_code(self):
return self.office_area_code
def get_office_number(self):
return self.office_number
class TelephoneNumber:
def __init__(self, area_code, number):
self.area_code = area_code
self.number = number
def to_string(self):
return f"({self.area_code}) {self.number}"
class Person:
def __init__(self, name, area_code, number):
self.name = name
self.office_telephone = TelephoneNumber(area_code, number)
def get_telephone_number(self):
return self.office_telephone.to_string()
Steps:
- Decide how to split the responsibilities of the class.
- Create a new class to express the split-off responsibility.
- Use a field in the old class to hold an instance of the new class.
- Move fields and methods from the old class to the new class.
- Update callers.
Inline Class
Problem: A class is doing too little to justify its existence.
Solution: Move all its features into another class and delete it.
class TelephoneNumber:
def __init__(self, area_code, number):
self.area_code = area_code
self.number = number
def to_string(self):
return f"({self.area_code}) {self.number}"
class Person:
def __init__(self, name, area_code, number):
self.name = name
self.office_telephone = TelephoneNumber(area_code, number)
def get_telephone_number(self):
return self.office_telephone.to_string()
class Person:
def __init__(self, name, area_code, number):
self.name = name
self.office_area_code = area_code
self.office_number = number
def get_telephone_number(self):
return f"({self.office_area_code}) {self.office_number}"
Steps:
- Adjust all methods in the delegating class to use the delegate class directly.
- Change all callers to use the delegate class directly.
- Delete the delegate class.
When to use: When Extract Class was done prematurely, or when the extracted class no longer carries its weight.
Hide Delegate
Problem: A client calls a method on a delegate object that's obtained from a server object.
Solution: Create a method on the server that delegates to the delegate.
class Department:
def __init__(self, manager):
self.manager = manager
class Person:
def __init__(self, department):
self.department = department
manager = john.department.manager
class Department:
def __init__(self, manager):
self.manager = manager
class Person:
def __init__(self, department):
self._department = department
def get_manager(self):
return self._department.manager
manager = john.get_manager()
Steps:
- For each method on the delegate that the client calls, create a method on the server that delegates to the delegate.
- Change the client to call the server's methods.
- If the client no longer needs the delegate, remove the accessor for it.
Remove Middle Man
Problem: A class has too many methods that do nothing but delegate to another.
Solution: Have the client call the delegate directly.
class Person:
def __init__(self, department):
self._department = department
def get_manager(self):
return self._department.manager
def get_department_name(self):
return self._department.name
def get_department_budget(self):
return self._department.budget
def get_department_headcount(self):
return self._department.headcount
class Person:
def __init__(self, department):
self.department = department
manager = john.department.manager
name = john.department.name
Steps:
- Expose the delegate object directly (make it a public attribute or return it from a getter).
- Change all clients to call through the delegate directly.
- Remove the delegating methods.
When to use: This is the inverse of Hide Delegate. Use it when Hide Delegate went too far and the server class became a bloated proxy.
Summary Table
| Refactoring | Problem | Solution |
|---|
| Move Method | Method uses data from another class | Move method to the class it uses most |
| Move Field | Field is used by another class | Move field to the class that uses it |
| Extract Class | One class does the work of two | Create new class, move relevant fields/methods |
| Inline Class | Class does too little | Merge into another class, delete it |
| Hide Delegate | Client reaches through server to delegate | Add delegation method on server |
| Remove Middle Man | Too many delegating methods | Let clients access the delegate directly |
Source: Martin Fowler, "Refactoring: Improving the Design of Existing Code" (2nd Edition)