Module 02 · SOLID and friends · Lesson 7 of 7
Law of Demeter
Talk to your immediate collaborators, not to their collaborators. Why chains like order.getCustomer().getAddress().getCity() couple code to the whole object graph, how 'tell, don't ask' fixes it, and the cases where chaining is fine.
Why it matters
A line like order.getCustomer().getWallet().getBalance().subtract(total) works today and ties the caller to four classes and their internal structure. Move the wallet onto an account, and every such chain across the codebase breaks. Worse, the caller is now making a decision (does the customer have enough money?) that belongs to the objects it reached through. The Law of Demeter is a simple, checkable rule that keeps objects from reaching through each other.
The idea
Also called the principle of least knowledge: a method should only call methods on
- its own object (
this), - its parameters,
- objects it creates,
- its own fields (direct collaborators).
It should not call methods on objects returned by those calls. Informally: one dot, not a train of them. Chains of getters reaching deep into an object graph are sometimes called train wrecks.
Tell, don't ask
The fix is rarely "add pass-through getters". It is to move the behaviour to the object that has the information, then tell it what to do instead of asking for its data and deciding outside.
// Asks and decides outside: coupled to Customer, Wallet and Money internals
if (order.getCustomer().getWallet().getBalance().isAtLeast(total)) {
order.getCustomer().getWallet().debit(total);
}
// Tells: each object handles its own part
order.pay(total); // Order -> customer.charge(total) -> wallet.debit(total) or throw
Now the rule "a customer can only pay what they have" lives in one place, and restructuring how customers hold money changes one class.
When chains are fine
The law targets navigation through objects with behaviour and hidden structure. It does not apply to:
| Case | Why it is fine |
|---|---|
| Fluent builders and query APIs: query.where(…).orderBy(…).limit(10) | Each call returns the same kind of object (or a builder), not a deeper part of a graph |
| Stream pipelines: items.stream().filter(…).map(…).toList() | Same reason: transformations of one abstraction |
| Plain data structures and DTOs: response.data.items[0].id | They are data with no behaviour to hide; navigating them is their purpose |
| Value objects: address.city().name() | Immutable values with stable, public structure |
A pragmatic reading
Count the distinct types a method has to know about, not the dots. If a method needs to understand the internal structure of objects three hops away, that knowledge is in the wrong place. Use the law as a smell detector during review, not as a mechanical rule; blindly adding wrapper methods to satisfy it can create bloated classes that forward everything.
Trade-offs
Following the law reduces coupling to the shape of the object graph and pushes behaviour to where the data lives, at the cost of more methods on intermediate objects. Applied mechanically, it produces forwarding methods that add nothing. Applied as "tell, don't ask", it usually improves cohesion as well as coupling.
In an interview
Catch it in your own flow: "Rather than the parking lot reaching into the ticket, then the vehicle, then the size, the lot asks the ticket's vehicle to tell it whether it fits a spot: spot.canFit(vehicle). The size rules stay inside Vehicle and Spot." Interviewers notice when a design avoids long reach-through chains.
Check yourself
A shipping service contains order.getCustomer().getAddress().getCountry().getTaxZone(). Requirements change so that addresses belong to a delivery profile rather than the customer, and dozens of call sites break. Which design would have contained the change?