Refactoring techniqueMoving Features between Objects
Hide Delegate
Add a method on the server object that forwards to its delegate, so clients stop navigating through it to reach the delegate.
Problem
Clients call a.getB().getC().doThing(). They depend on the structure between a, b and c, so restructuring any link breaks every chain in the codebase.
Solution
Give the first object a method that does the navigation internally, and have clients call that method instead. The client then knows one object, not the path behind it.
Before
// In several controllers and jobs:
const approverEmail = order.getAccount().getBillingContact().email;
const region = order.getAccount().getBillingContact().address.region;
class Order {
constructor(private account: Account) {}
getAccount(): Account { return this.account; }
}After
const approverEmail = order.billingContactEmail();
const region = order.billingRegion();
class Order {
constructor(private account: Account) {}
billingContactEmail(): string {
return this.account.getBillingContact().email;
}
billingRegion(): string {
return this.account.getBillingContact().address.region;
}
}Why
- If billing contacts move from
Accountto a separateBillingProfile, onlyOrderchanges. - Clients express intent (
billingRegion()) instead of navigation. - Fewer types appear in client imports, so tests need fewer mocks.
Steps
- Find a chain clients repeat. Pick the object they start from as the server.
- Add a method on the server that performs the navigation and returns the needed value or performs the action.
- Replace one client chain with the new method. Run the tests.
- Replace the remaining chains.
- If no client still needs the delegate itself, remove or narrow the server's accessor (
getAccount()).
Watch out
- Don't hide every delegate. Each forwarding method enlarges the server. When most of the server's methods are forwarders, it has become a Middle Man.
- Chains through plain data structures or fluent builders (
query.where(...).orderBy(...)) are not this smell. Leave them. - Forwarding methods should stay thin. If logic accumulates in them, it may belong in the delegate.
- The inverse is Remove Middle Man. Use it when forwarding has taken over.
Related
- Fixes Message Chains and Inappropriate Intimacy.
- Inverse: Remove Middle Man.
- Facade applies the same idea to a whole subsystem.
Agent checklist
- The chain appears in more than one client, or its structure is likely to change.
- The new method is named for what the client wants, not the path it takes.
- All occurrences of the chain were replaced.
- The server did not turn into a class of mostly forwarders.
- Tests pass.
Treats these smells
See also
- Remove Middle ManLet clients call the delegate directly when a class does little more than forward calls to it.
- Move MethodMove a method to the class whose data it uses most, and leave a delegating call or update the callers.
- Extract MethodMove a coherent fragment of a long function into its own well-named function and call it from the original place.
- FacadeGive a complex subsystem one small entry point for a common task, so callers stop orchestrating its classes and calling them in the right order.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.