Hide Delegate: Ask the Monitor, Let the Monitor Do the Running
Learn the Hide Delegate refactoring with a story about a class monitor who finds your homework for you. Stop writing chains like employee.department.manager — give the first object a simple method and hide the journey inside it. Step-by-step TypeScript and C# examples included.
🏫 The day Riya was absent
Riya studies in class 8 at a school in Pune. On Monday she had high fever, so she stayed home. On Tuesday morning she walks into class with one simple question in her head: what is my homework?
In Riya's school, sadly, this is a long adventure. The class monitor, Priya, keeps the period timetable. So Riya first goes to Priya and asks, "Priya, who took the third period yesterday?" Priya checks her diary and says, "Sharma sir." Now Riya walks down two corridors to the staff room, finds Sharma sir between his chai and his attendance register, and asks, "Sir, what homework did you give yesterday?" He tells her: pages 10 to 12. Riya writes it down, walks back, and starts the whole circus again for the fourth period. Then the fifth. By lunch break she has done more walking than studying.
Stop and notice what Riya had to know to finish this errand:
- She had to know that Priya holds the timetable — not the notice board, not the class diary.
- She had to know which teacher belongs to which period — yesterday's substitute arrangements included.
- She had to know where each teacher sits — staff room, lab, or sports ground.
All this knowledge, just to collect a homework list. Worse: if the school changes any one of these details — the timetable moves to a notice board, a new substitute teacher comes, the staff room shifts to the new building — Riya's entire routine breaks, and she must learn the new path from scratch.
Now imagine the better school. Riya walks up to Priya and asks one question: "Priya, what is my homework?" Priya checks her diary, asks the teachers herself during break, and hands Riya the complete list. Riya does not know whether Priya asked Sharma sir directly, read the notice board, or copied from her own notes. That is Priya's business. Riya asked one trusted person, one question, and got one answer.
Here is the beautiful part: in the new school, the walking still happens. Priya still talks to the teachers. The work did not disappear — it moved behind Priya, where Riya cannot see it and does not depend on it.
This is exactly what the Hide Delegate refactoring does in code. When your code reaches through one object to grab a second object and then calls something on it, you are doing Riya's staff-room walk yourself. Hide Delegate says: stop walking. Give the first object a simple method, and let it do the running internally.
🔍 What is Hide Delegate?
Hide Delegate is a refactoring from Martin Fowler's book Refactoring. The recipe is short: when a client asks an object for one of its parts and then calls a method on that part, add a new method on the first object that makes the second call internally. The part — called the delegate — disappears from the client's view.
Look at the famous example. The client wants an employee's manager:
// The client digs through Employee to reach Department:
const manager = employee.department.manager;The client here knows two facts: an employee has a department, and a department has a manager. That second fact is none of the client's business. After Hide Delegate:
class Employee {
constructor(private department: Department) {}
get manager(): Person {
return this.department.manager; // the hop happens inside
}
}
// The client asks one friend, one question:
const manager = employee.manager;The chain of two hops became one direct question. Department is now a private detail of Employee. If tomorrow the company decides that a manager comes from a project team instead of a department, only Employee changes. Every client keeps working, because every client only ever said employee.manager.
In class-diagram form, the change is tiny but powerful: the public road from caller to Department closes, and a small new method opens on Employee.
The big idea has a name: the Law of Demeter, often said as "talk to friends, not to strangers." Your method should talk to objects it directly holds, receives as parameters, or creates — not to objects fished out of other objects.
College corner: The Law of Demeter (Lieberherr and Holland, 1989) is formally stated for a method M of class C: M may only invoke methods of C itself, M's parameters, objects M creates, and C's direct component objects. The object returned by getB() is none of these — it is a "stranger" — so a.getB().getC() violates the law. The cost being controlled is coupling to representation: every chain a client writes is a structural assumption frozen into the client. Some authors soften the law to "use only one dot," which is a heuristic, not the law itself — a fluent builder like query.where(x).orderBy(y) has many dots but zero structural leakage, because every call returns the same kind of object. Judge by what knowledge leaks, not by counting dots.
A simple test for your code: read the line aloud. "Employee's department's manager" — two possessives means you are describing someone else's internal structure. "Employee's manager" — one possessive, one friend, one question. Aim for one.
🚦 When do we need it?
Reach for Hide Delegate when you see these signs:
- Train wrecks in client code. Lines like
order.customer.address.citywith dot after dot after dot. This is the Message Chains smell, and Hide Delegate is its number-one cure. - The same chain repeated everywhere. If twenty files all write
employee.department.manager, then twenty files all break when the structure changes. One hidden hop fixes all twenty. - Clients knowing too much. When client code depends on how objects connect inside another object, that is Inappropriate Intimacy. Hiding the delegate restores privacy.
- A structure you expect to change. If you already suspect that "manager comes from department" will not stay true forever, hide it now, cheaply, before fifty call sites copy the assumption.
| Signal | What it looks like | Strength |
|---|---|---|
| Train wreck | a.b.c.d in client code | Strong — refactor soon |
| Repeated chain | Same path in many files | Very strong — one change breaks all |
| Volatile structure | The middle link is likely to change | Strong — hide before it spreads |
| One short, stable, local chain | order.id style access in one place | Weak — leave it alone |
Why does the repeated chain matter so much? Because in a real codebase, most of the pain from a structure change does not come from the class you edited — it comes from every caller that memorised the old path.
And one sign that you should stop: if you are about to add the tenth forwarding method to the same class, pause. You may be manufacturing a Middle Man — a class that does nothing but forward. The cure for that is the exact opposite refactoring, Remove Middle Man. We will talk about this balance soon.
👀 Before and after at a glance
Here is the smallest complete picture:
// ---------- BEFORE ----------
class Department {
constructor(public manager: Person, public chargeCode: string) {}
}
class Employee {
constructor(public department: Department) {}
}
// Clients everywhere walk the chain:
const manager = employee.department.manager;
const code = employee.department.chargeCode;// ---------- AFTER ----------
class Employee {
constructor(private department: Department) {}
get manager(): Person {
return this.department.manager;
}
get chargeCode(): string {
return this.department.chargeCode;
}
}
// Clients ask one object:
const manager = employee.manager;
const code = employee.chargeCode;The department field went from public to private. That single keyword is the prize: nobody outside can depend on the employee–department link any more.
🪜 Step-by-step, the safe way
Refactoring is not "rewrite and pray." It is small steps with tests after each step. Here is the safe routine for Hide Delegate.
Step 1 — Find the chain. Search the codebase for the pattern server.delegate.something. List every delegate method that clients use. Suppose clients use employee.department.manager and employee.department.chargeCode.
Step 2 — Add one forwarding method on the server. Do not touch any client yet. Just add the new door:
class Employee {
constructor(public department: Department) {} // still public for now!
get manager(): Person {
return this.department.manager;
}
}Compile and run the tests. Everything still passes, because nothing else changed.
Step 3 — Redirect clients one by one. Change employee.department.manager to employee.manager at one call site. Test. Next call site. Test. Boring? Yes. Safe? Completely. If a test fails, you know exactly which one-line change caused it.
Step 4 — Repeat for each delegate method. Add get chargeCode(), redirect its callers the same way.
Step 5 — Lock the door. When no client touches employee.department directly any more, make the field private:
class Employee {
constructor(private department: Department) {}
// ...getters as above
}If the compiler complains, congratulations — it just found a call site you missed. Fix it and try again.
Step 6 — Review the server's interface. Look at Employee now. Does every new method earn its place? If you added two or three meaningful ones, great. If you added fifteen pure forwards, you have over-corrected; read the balance section below.
The whole life cycle of a codebase around this refactoring looks like a slow swing, and your job is to keep nudging it back to the middle:
Never do steps 2 to 5 in one giant commit without running tests in between. The whole safety of refactoring comes from the rhythm: tiny change, green tests, tiny change, green tests. If your project has no tests around this code, write a few characterization tests first — even simple ones that assert today's behavior — before moving anything.
🏫 A bigger real-life example
Let us take something closer to a real project: a school result portal — Riya's school, in fact, after it got digitised. The client code prints a student's result card, and today it digs deep:
class School {
constructor(public principal: Person, public board: string) {}
}
class ClassSection {
constructor(public school: School, public classTeacher: Person) {}
}
class Student {
constructor(
public name: string,
public section: ClassSection,
public marks: Map<string, number>
) {}
}
// Client: the result card printer — full of train wrecks
function printResultCard(student: Student): string {
const teacher = student.section.classTeacher.name;
const board = student.section.school.board;
const principal = student.section.school.principal.name;
return `${student.name} | Teacher: ${teacher} | Board: ${board} | Signed: ${principal}`;
}The printer knows the entire school hierarchy: student → section → school → principal. Four classes, three links. If the school ever supports a student belonging to two sections, or sections that share a coordinating teacher, this function breaks — and so does every other function that walked the same path.
Apply Hide Delegate. Each object answers questions about its own world:
class ClassSection {
constructor(private school: School, private classTeacher: Person) {}
get teacherName(): string {
return this.classTeacher.name;
}
get boardName(): string {
return this.school.board; // school hop hidden here
}
get principalName(): string {
return this.school.principal.name;
}
}
class Student {
constructor(
public name: string,
private section: ClassSection,
public marks: Map<string, number>
) {}
get teacherName(): string {
return this.section.teacherName;
}
get boardName(): string {
return this.section.boardName;
}
get principalName(): string {
return this.section.principalName;
}
}
// Client: now it talks to ONE friend
function printResultCard(student: Student): string {
return `${student.name} | Teacher: ${student.teacherName} | Board: ${student.boardName} | Signed: ${student.principalName}`;
}Notice something honest here: we added three forwarding getters to Student and three to ClassSection. That is a cost. If tomorrow someone needs ten more school facts on every student, forwarding all of them through two layers becomes silly — at that point the design question is different (maybe the printer should receive a School directly). Hide Delegate is for hiding structure that clients should not know, not for tunnelling an entire API through layers.
College corner: This cost has a name in design literature: delegation overhead. Each forwarding method is a line of code that must be written, read, tested, and kept in sync with the delegate's signature. The trade is overhead now versus change amplification later — the Shotgun Surgery effect where one structural edit fans out into dozens of call-site edits. A useful way to reason about it: a chain written in N call sites costs N edits per structure change; a hidden hop costs 1 edit per structure change plus 1 forwarding method per exposed operation. Hide Delegate wins when call sites outnumber the operations being forwarded, which in mature codebases they almost always do.
💻 The same refactoring in C#
C# makes Hide Delegate very natural with properties. Before:
public class Department
{
public Person Manager { get; }
public Department(Person manager) => Manager = manager;
}
public class Employee
{
public Department Department { get; }
public Employee(Department department) => Department = department;
}
// Client code, scattered:
var manager = employee.Department.Manager;After — the delegate becomes a private field, and a one-line expression-bodied property forwards the call:
public class Employee
{
private readonly Department _department;
public Employee(Department department) => _department = department;
public Person Manager => _department.Manager; // hop hidden inside
}
// Client code:
var manager = employee.Manager;The C# compiler is your friend in step 5: change public Department Department to a private field, build, and every leftover chain becomes a compile error you can fix mechanically. In large solutions, this "let the compiler find the callers" trick is one of the safest ways to finish the refactoring.
🐍 And once in Python
Python has no private keyword, but the same refactoring works through convention and properties:
class Employee:
def __init__(self, department):
self._department = department # underscore = please treat as private
@property
def manager(self):
return self._department.manager # the hop, hidden
# Client code:
print(employee.manager) # good — one friend, one question
# print(employee._department) # possible, but every reader knows it is trespassingPython cannot stop a determined trespasser, but the underscore plus a clean public property makes the right path the easy path — and in code review, employee._department.manager in client code is an instant red flag.
📈 Is your codebase drifting? Read the dials
How do you measure whether your project needs this refactoring? Two simple charts tell the story. First, track how many distinct files contain a chain through the same link. This number creeping up is the sound of coupling spreading:
Second, when deciding which refactoring to apply, place your class on this map. Long chains everywhere with wrappers that would add real meaning? Hide Delegate. A class already stuffed with forwards that add nothing? The opposite move.
In our story, the result portal started at the right side of the map (chains everywhere) — clear Hide Delegate territory. Two years later, after enthusiastic wrapping, it had drifted to the top-left — time for the opposite medicine.
⚖️ The seesaw: Hide Delegate vs Remove Middle Man
Here is the most important lesson of this whole post. Hide Delegate has an exact opposite: Remove Middle Man. They sit on the two ends of one seesaw.
- Too many chains in client code → clients know too much structure → apply Hide Delegate, add forwarding.
- Too much pure forwarding in a class → the class is an empty toll booth → apply Remove Middle Man, delete forwarding and expose the delegate.
There is no permanent right answer — Fowler himself says the right amount of hiding changes as the system changes. Yesterday's helpful hidden hop may become today's useless forward. Refactor toward the middle, in whichever direction you currently lean.
One picture to hold the whole idea in your head:
✅ Benefits and risks
| Benefit | Why it matters |
|---|---|
| Clients depend on one object, not a path | A change inside the server cannot break callers |
| The delegate can become private | True encapsulation — the link is no longer public API |
| Call sites read as intent | employee.manager says what, not how to find it |
| Structure can evolve freely | Swap the delegate, derive the value differently — clients never notice |
| Risk | How to handle it |
|---|---|
| Server's interface grows with forwarding methods | Only hide the chains clients actually use; review the interface after |
| Too much hiding creates a Middle Man | Watch the ratio of forwarding methods to real ones |
| Rich access needs become awkward | If a client needs many delegate methods, rethink the boundary instead of forwarding all |
| Hidden hop may surprise readers | Name the method by its meaning, not by the path |
🧪 Which smells does it cure?
| Smell | How Hide Delegate helps |
|---|---|
| Message Chains | The direct cure — collapses a.b.c into one intention-revealing call |
| Inappropriate Intimacy | Clients stop knowing the server's internal collaborators |
| Shotgun Surgery (structural changes) | One structure change edits one class, not every call site |
| Middle Man | ⚠️ Does NOT cure it — over-applying Hide Delegate creates it |
📦 Quick revision box
+--------------------------------------------------------------+
| HIDE DELEGATE — CHEAT SHEET |
+--------------------------------------------------------------+
| Smell to spot : client writes server.delegate.method() |
| Move : add server.method() that forwards internally |
| Then : redirect callers one by one, test each time |
| Finally : make the delegate field PRIVATE |
| Law obeyed : Law of Demeter — talk to friends only |
| Cures : Message Chains, Inappropriate Intimacy |
| Danger : too many forwards -> Middle Man smell |
| Opposite move : Remove Middle Man (same dial, other end) |
| Safety rule : tiny step -> run tests -> repeat |
+--------------------------------------------------------------+✏️ Practice exercise
Here is a small library system with chains in it. Refactor it yourself.
class Rack {
constructor(public hall: string, public floor: number) {}
}
class Book {
constructor(public title: string, public rack: Rack) {}
}
class Member {
constructor(public name: string, public borrowed: Book[]) {}
}
// Client code — three train wrecks:
function whereIsMyBook(member: Member, title: string): string {
const book = member.borrowed.find(b => b.title === title)!;
return `Hall ${book.rack.hall}, floor ${book.rack.floor}`;
}
function overdueNotice(member: Member): string {
const halls = member.borrowed.map(b => b.rack.hall).join(", ");
return `${member.name}, please return books to: ${halls}`;
}Your tasks:
- Find every chain that reaches through
BookintoRack. How many call sites depend on the book–rack link? - Add a
locationgetter (returning something like"Hall A, floor 2") onBookso that callers stop touchingrackdirectly. Redirect both functions, one at a time, running your tests between changes. - Make
rackprivate onBook. Does everything still compile? - Draw your own version of Figure 10 and place this library code on the map — before and after your change. Did you move it toward the healthy quadrant or past it?
- Bonus thinking question: should
Memberalso hide itsborrowedarray behind methods likefindBook(title)? Or would that start turningMemberinto a Middle Man? Write one sentence defending your choice — that sentence is the seesaw judgement every professional makes.
When you can explain why you stopped hiding where you stopped, you have truly understood Hide Delegate. And when your hiding goes too far — when a class becomes nothing but a polite forwarding desk — the next post is waiting with the opposite medicine.
Frequently asked questions
- What is the Hide Delegate refactoring?
- Hide Delegate adds a small forwarding method to an object so that callers stop reaching through it to a second object. Instead of writing employee.department.manager, you give Employee a manager getter that asks its own department internally. The caller now talks to one object only, and the hidden object (the delegate) becomes a private detail.
- How does Hide Delegate relate to the Law of Demeter?
- The Law of Demeter says: talk only to your direct friends, not to strangers reached through them. A chain like a.getB().getC() talks to a stranger — the object returned by getB(). Hide Delegate is the practical way to obey the law: the first object gets a method that does the second hop inside itself, so the caller never meets the stranger.
- When should I NOT use Hide Delegate?
- Do not hide a delegate when callers genuinely need many of its methods. Forwarding ten methods one by one bloats the server and slowly turns it into a Middle Man — a class that only forwards calls. Also leave short, stable, local chains alone; not every dot is a problem.
- Is Hide Delegate the opposite of Remove Middle Man?
- Yes, exactly. Hide Delegate adds forwarding methods to kill message chains. Remove Middle Man deletes forwarding methods when a class has become pure forwarding. They are two ends of one dial. Healthy code sits in the middle: hide the chains that leak structure, but do not wrap every single call.
- Does Hide Delegate change behavior or performance?
- No behavior changes — the same calls happen, just inside the server instead of at the call site. The extra method call is negligible in almost every program. What changes is coupling: callers no longer know the path, so the server can rearrange its internals without breaking anyone.
Further reading
Related Lessons
Message Chains: Asking a Friend, Who Asks a Cousin, Who Asks an Uncle
Learn the Message Chains code smell with a story about finding out if bread is available — through four people. When code reads a.getB().getC().getD(), the caller is coupled to a whole path. Learn the Law of Demeter and cure chains with Hide Delegate.
Middle Man: The Helper Who Only Forwards Your Message to the Principal
Learn the Middle Man code smell with a story of a school helper who only carries messages without adding anything. When a class merely forwards every call, remove it — but learn why Proxy, Facade, and Adapter are middle men ON PURPOSE.
Remove Middle Man: When the Peon Only Forwards, Meet the Principal Directly
Learn the Remove Middle Man refactoring with a story about a school peon who forwards every question to the principal without adding anything. When a class only forwards calls to its delegate, delete the forwarding and let clients talk to the delegate directly. Step-by-step TypeScript and C# walkthrough.
Inappropriate Intimacy: Two Classes That Walk Into Each Other's Kitchens
Learn the Inappropriate Intimacy code smell with a story of two neighbours who rearrange each other's kitchens. When two classes poke each other's private parts, neither can change alone. Learn the Law of Demeter and the refactorings that restore privacy.