Hide Delegate and Remove Middle Man
Intent
Section titled “Intent”Hide Delegate: when a client reaches through one object to call a method on a second — person.department.manager — give the first object its own method that hides the hop, so the client just calls person.manager(). The client stops depending on the structure behind person.
Remove Middle Man is the inverse: when an object is doing little but forwarding call after call to its delegate, let the client talk to the delegate directly and delete the forwarding methods.
The smell
Section titled “The smell”Hide Delegate cures Inappropriate Intimacy with a class’s internals and is the practical face of the Law of Demeter: talk to your friends, not your friends’ friends. A chain like a.getB().getC().doIt() couples the caller to two relationships it has no business knowing about. The reverse smell is the Middle Man: a class whose methods nearly all read “return delegate.something()” — so much forwarding that the wrapper adds nothing.
Before → After
Section titled “Before → After”A Person whose manager is found through their Department. Before, the client navigates the chain. After, Person hides the delegate behind a manager() method. (Apply Remove Middle Man to go back the other way when the wrapper stops earning its place.)
// Before — client walks person.department.managerclass Department { constructor(public manager: string) {}}
class Person { constructor(public name: string, public department: Department) {}}
const managerName = person.department.manager;
// After — Hide Delegateclass Department { constructor(private _manager: string) {} get manager(): string { return this._manager; }}
class Person { constructor(public name: string, private department: Department) {} manager(): string { return this.department.manager; }}
const managerName = person.manager();# Before — client walks person.department.managerclass Department: def __init__(self, manager): self.manager = manager
class Person: def __init__(self, name, department): self.name = name self.department = department
manager_name = person.department.manager
# After — Hide Delegateclass Department: def __init__(self, manager): self._manager = manager
@property def manager(self): return self._manager
class Person: def __init__(self, name, department): self.name = name self._department = department
def manager(self): return self._department.manager
manager_name = person.manager()// Before — client walks person.Department.Managertype Department struct { Manager string}
type Person struct { Name string Department Department}
managerName := person.Department.Manager
// After — Hide Delegatetype Department struct { manager string}
func (d Department) Manager() string { return d.manager}
type Person struct { Name string department Department}
func (p Person) Manager() string { return p.department.Manager()}
managerName := person.Manager()// Before — client walks person.department.managerstruct Department { manager: String,}
struct Person { name: String, department: Department,}
let manager_name = &person.department.manager;
// After — Hide Delegatestruct Department { manager: String,}
impl Department { fn manager(&self) -> &str { &self.manager }}
struct Person { name: String, department: Department,}
impl Person { fn manager(&self) -> &str { self.department.manager() }}
let manager_name = person.manager();flowchart LR
subgraph Before["Before — caller walks the chain"]
A["client"] --> B["person.department"]
B --> C[".manager"]
end
subgraph After["After — Hide Delegate"]
D["client"] --> E["person.manager()"]
E -.->|"internally"| F["department.manager"]
end
Before -.->|"Hide Delegate"| After
After -.->|"Remove Middle Man"| Before Mechanics
Section titled “Mechanics”Hide Delegate
- For each method on the delegate the client reaches for, add a simple delegating method on the server (the object the client already holds).
- Update the client to call the new server method instead of walking the chain.
- Run your tests after each method you redirect.
- Once no client navigates to the delegate directly, you can hide the accessor that exposed it.
Remove Middle Man (the inverse)
- Add an accessor on the server that returns the delegate itself.
- For each delegating method that adds no value, replace its callers with a direct call through that accessor.
- Run your tests as you go.
- Delete the delegating methods once they have no callers left.
When to use / trade-offs
Section titled “When to use / trade-offs”Hide a delegate when clients are reaching through an object to its neighbours, when you want freedom to change the relationship behind an object without breaking every caller, or when a chain has grown long enough to read like a navigation instruction.
The risk is overshoot: hide every delegate and the server fills with forwarding methods until it becomes a Middle Man. That is precisely when you reach for Remove Middle Man and let clients talk to the delegate again. The two moves are a dial — turn it toward encapsulation when the structure is volatile, toward directness when the wrapper is just noise.