Skip to main content
CleanCodeMastery

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.

20 min read Updated June 11, 2026beginner
refactoringshide-delegatelaw-of-demetermessage-chainsencapsulationcouplingtypescriptcsharp

🏫 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.

Figure 1: Riya's Tuesday, scored by mood. The old way is a tiring treasure hunt; the new way is one happy question.

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.

Figure 2: The new school in motion. Riya asks one question; the monitor does the second hop internally.

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.

Figure 3: The same request, two shapes. The chain version couples the caller to the path; the hidden version couples it to one method name.

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.

Figure 4: After Hide Delegate. Employee offers getManager and privately keeps its Department. Callers never touch Department.

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:

  1. Train wrecks in client code. Lines like order.customer.address.city with dot after dot after dot. This is the Message Chains smell, and Hide Delegate is its number-one cure.
  2. 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.
  3. Clients knowing too much. When client code depends on how objects connect inside another object, that is Inappropriate Intimacy. Hiding the delegate restores privacy.
  4. 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.
SignalWhat it looks likeStrength
Train wrecka.b.c.d in client codeStrong — refactor soon
Repeated chainSame path in many filesVery strong — one change breaks all
Volatile structureThe middle link is likely to changeStrong — hide before it spreads
One short, stable, local chainorder.id style access in one placeWeak — 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.

Figure 5: Where the pain of one structure change usually lands. The class you changed is the small slice; the call sites that knew the path are the big one.

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.

Figure 6: Count the arrows leaving the client. Before: two dependencies. After: one. Fewer arrows means fewer reasons to break.

🪜 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:

Figure 7: The life cycle of delegation in a real codebase. Hide Delegate moves you out of chain-land; years of habit can overshoot into wrapper-land; Remove Middle Man brings you back.
⚠️

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}`;
}
Figure 8: The result card printer asks Student. Student quietly asks Section. Section quietly asks School. Each object only talks to its direct friend.

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 trespassing

Python 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:

Figure 9: Files containing the employee.department.x chain across releases. Every new file that copies the chain is one more future breakage.

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.

Figure 10: The decision map. Where your code sits decides which way to turn the dial.

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.
Figure 11: The delegation dial. Left extreme: train wrecks everywhere. Right extreme: classes that only forward. Healthy code sits in the middle, and you turn the dial with these two opposite refactorings.

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:

Figure 12: The delegation dial as a mind map — two failure modes, two cures, one balance point.

✅ Benefits and risks

BenefitWhy it matters
Clients depend on one object, not a pathA change inside the server cannot break callers
The delegate can become privateTrue encapsulation — the link is no longer public API
Call sites read as intentemployee.manager says what, not how to find it
Structure can evolve freelySwap the delegate, derive the value differently — clients never notice
RiskHow to handle it
Server's interface grows with forwarding methodsOnly hide the chains clients actually use; review the interface after
Too much hiding creates a Middle ManWatch the ratio of forwarding methods to real ones
Rich access needs become awkwardIf a client needs many delegate methods, rethink the boundary instead of forwarding all
Hidden hop may surprise readersName the method by its meaning, not by the path

🧪 Which smells does it cure?

SmellHow Hide Delegate helps
Message ChainsThe direct cure — collapses a.b.c into one intention-revealing call
Inappropriate IntimacyClients 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:

  1. Find every chain that reaches through Book into Rack. How many call sites depend on the book–rack link?
  2. Add a location getter (returning something like "Hall A, floor 2") on Book so that callers stop touching rack directly. Redirect both functions, one at a time, running your tests between changes.
  3. Make rack private on Book. Does everything still compile?
  4. 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?
  5. Bonus thinking question: should Member also hide its borrowed array behind methods like findBook(title)? Or would that start turning Member into 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