skip to content

What is the Message Chains code smell, how does it relate to the Law of Demeter, and why can "fixing" it with Hide Delegate produce the Middle Man smell?

level: seniorimportance: should knowfreq 54%

answer

  1. a.getB().getC().getD()
  2. Law of Demeter = least knowledge, not dot-counting
  3. Hide Delegate ↔ Remove Middle Man dial
  4. builders and pipelines are exempt
  5. move the behaviour, don't wrap the navigation

basics

~20 s

A message chain is code like a.getB().getC().getD() — the caller navigates a long path through objects. It breaks the Law of Demeter ("only talk to your immediate friends"). Hiding each hop behind a delegating method can leave classes that only forward calls: Middle Man.

solid answer

~60 s

**Message Chains** — a client asks an object for another object, then asks that one for another, and so on. The client becomes coupled to the *shape of the object graph*: any change to an intermediate structure breaks it, and the same navigation is duplicated across clients. The **Law of Demeter** (principle of least knowledge, Lieberherr, 1987) formalises the rule: a method should only call methods of itself, its parameters, objects it creates, and its own fields — not objects returned by those calls. It is a heuristic about *coupling to structure*, not a ban on dots; fluent builders and pipelines chain on the same object and are unaffected. Fixes: **Hide Delegate** (the server exposes a method that does the navigation), **Extract Function + Move Function** to push the computation to where the data lives, or Tell-Don't-Ask so the end object does the work. Over-applying Hide Delegate produces **Middle Man**: a class where most methods only forward. The cure there is **Remove Middle Man** — let the client talk to the delegate directly. The two smells are opposite ends of one dial, and the balance is judged per relationship.

code

pseudocode · 8 lines
pseudocode
// Message Chain: caller knows the whole graph
city = order.getCustomer().getAddress().getCity().getName()

// Hide Delegate: caller knows one type
city = order.customerCityName()

// Better: Tell, Don't Ask — move the computation to the data
order.shipTo(labelPrinter)   // Order asks its own Customer/Address

go deeper

for a junior

Recognise the chained-getter shape, state the Law of Demeter informally as "talk only to your immediate friends", and name Hide Delegate.

for a middle

State the formal Demeter rule, explain structural coupling and duplication as the real costs, and know that over-hiding yields Middle Man with Remove Middle Man as its cure.

for a senior

Discuss the dial and per-relationship judgement, the data-structure and builder/pipeline exemptions, and prefer moving behaviour to the data over wrapping navigation.

for a principal

Frame it as interface-and-boundary design: which types are published and stable versus volatile internals, how anti-corruption layers and facades legitimately delegate, and the coupling cost of pass-through interfaces that grow with their delegates.

## Message Chains **Definition.** A client navigates a sequence of objects to reach the one it actually needs: ``` city = order.getCustomer().getAddress().getCity().getName() ``` **Why it hurts.** - **Structural coupling.** The client depends not just on `Order` but on `Customer`, `Address`, and `City` and on how they are linked. Any change to that chain — inserting a `BillingProfile`, making address a collection — breaks every client. - **Duplication.** The same navigation is copy-pasted across many callers, so the change cost multiplies. - **Null/absence handling.** Each hop can be absent, so real code grows guards at every link, or crashes. - **Hidden intent.** The chain says *how to get there*, never *what is wanted*. **Refactorings.** 1. **Hide Delegate** — the first object exposes what the caller wants: `order.customerCity()`. The client now knows one type instead of four. 2. **Extract Function + Move Function** — better still, move the *computation* to the object that owns the data, so nothing needs fetching at all (Tell, Don't Ask). 3. **Pass what is needed.** If the callee only wants a city name, pass the city name rather than the whole graph root. ## The Law of Demeter Proposed by Karl Lieberherr and colleagues (Northeastern University, 1987) as the *principle of least knowledge*. The object-form rule: a method `m` of object `O` may only invoke methods of - `O` itself, - `m`'s parameters, - objects `m` creates, - `O`'s direct component objects (fields), - (commonly added) globals/statics accessible to `O`. It may **not** invoke methods on objects *returned* by any of those calls. Hence the nickname "don't talk to strangers" and the crude test "one dot" — crude because the dot count is not what matters. **Important qualifications.** - **It is a heuristic, not a law.** Blindly enforcing it inflates interfaces with pass-through methods (exactly the Middle Man risk) and can *increase* total coupling. - **Data structures are exempt in spirit.** Navigating a plain data record (`config.server.port`), a builder (`b.name(x).age(y).build()`), or a functional pipeline (`items.filter(...).map(...).toList()`) is not the smell: builders and pipelines return *the same or a new value*, not a deeper object you are coupling to. The smell concerns *behaviour-bearing objects whose internal structure you are traversing*. - **The pain is real where the graph is unstable.** Chains through stable, published value types are cheap; chains through volatile internal domain structure are expensive. ## Middle Man — the over-correction **Definition.** A class whose methods mostly just delegate to another object, adding no behaviour, no policy, no translation. Hiding delegates is good in moderation: each hidden hop buys the client independence from one intermediate type. But if `Order` gains `customerCity()`, `customerPostcode()`, `customerCountry()`, `customerVatNumber()` … it has become a pass-through façade whose interface grows every time the client needs a new field, and every added method is a change in two places. **Refactoring: Remove Middle Man** — delete the forwarders and let the client obtain and use the delegate directly (`order.customer().city()`), or **Replace Delegation with Inheritance/Composition** where appropriate. **When delegation is justified and not a smell:** an Adapter or anti-corruption layer translating between models; a Proxy adding lazy loading, caching, retries, authorization, or instrumentation; a Facade deliberately narrowing a subsystem; a stable published interface insulating clients from a volatile implementation. "Delegating and adding nothing *yet*" for a boundary you intend to keep is a design decision, not automatically a smell. ## The dial Message Chains and Middle Man sit at opposite ends of one continuum: ``` client navigates everything client is told everything (Message Chains) <--------------------> (Middle Man) Hide Delegate --> <-- Remove Middle Man ``` Fowler's guidance is explicitly that you decide *per relationship*, and that reasonable designers land in different places. The productive question is: **which type do you want the client coupled to, and which types do you want free to change?** Hide the hops through volatile internals; expose stable, meaningful collaborators directly. ## Practical judgement - Prefer moving the *behaviour* to the data over hiding the *navigation* — it removes the chain instead of wrapping it. - Do not count dots; ask whether the intermediate types are ones you are willing to be coupled to. - A chain repeated in many clients is far more urgent than a single occurrence. - Chains over immutable value objects and configuration records are usually fine.

  • Is a fluent builder like `Pizza.builder().size(L).topping(X).build()` a Law of Demeter violation?
    No. Each call returns the same builder (or an equivalent new value in an immutable builder), so the caller is not traversing into progressively deeper, foreign object structure — the coupling is to one type, the builder. The same reasoning exempts collection/stream pipelines. The smell is about depending on the *shape of another object's internals*, not about the number of dots.
  • How do you decide between Hide Delegate and Remove Middle Man for a given relationship?
    Ask which types you want clients coupled to. Hide hops that traverse volatile internal structure or that recur across many clients; expose stable, meaningful collaborators directly rather than mirroring their whole interface. If a delegating class adds translation, policy, caching, or a deliberate boundary, keep it; if it only forwards and its interface grows in lockstep with the delegate's, remove it.

Asking a colleague for their manager's assistant's phone number couples you to their whole org chart; asking the colleague to "get this approved" does not. But if they must relay every single request, they become a switchboard that adds nothing.

saying these in an interview costs you the question

  • Reducing the Law of Demeter to "only one dot per line" and flagging builders, streams, or configuration records
  • Claiming the Law of Demeter is an inviolable law rather than a coupling heuristic
  • Hiding every delegate reflexively, inflating the intermediate interface until it is a Middle Man
  • Calling every delegating class a Middle Man, ignoring Adapters, Proxies, Facades, and anti-corruption layers
  • Treating null-safe navigation operators as a fix — they hide the crash but keep the structural coupling

context