// SOLID & Principles · 09
Law of Demeter
Talk to your direct collaborators, not to their insides.
How it fits together
- ShippingService uses / calls Order (1 order)
- Order uses / calls Customer (2 .Customer)
- Customer uses / calls Address (3 .Address)
- ShippingService uses / calls Order
- Order wraps / holds Customer
- Customer wraps / holds Address
Spot it
- Long chains like order.Customer.Address.City
- Tests build deep object graphs to run
- A model change breaks distant callers
It costs
Wrapper methods multiply; applied blindly, owners grow bloated pass-through APIs.
In code
public sealed record Address(string Country, string City);public sealed class Customer(Address address){ public bool IsIn(string country) => address.Country == country;}public sealed class Order(Customer customer){ public bool ShipsTo(string country) => customer.IsIn(country);}public sealed class ShippingService{ public bool Ship(Order order, string country) => order.ShipsTo(country);}Go deeperThe full explanation and what to ask for · 1 min readExpandCollapse
A method should only call methods on its own object, its parameters, objects it creates and its direct fields. Each extra hop couples the caller to another object's structure.
It is not "count the dots". Query pipelines, fluent builders and plain data access return values or the same object, so chaining them reaches into nothing. The concern is navigating through objects that own behaviour.
Fix it with tell, don't ask: move the behaviour to the object that holds the data. If that turns a class into a dumping ground, the boundary itself is wrong.
Ask for it
- principle of least knowledge
- tell, don't ask
- train wreck
- encapsulation
- coupling
Find method chains in the Application layer that reach through domain objects, like order.Customer.Address.Country. Replace them with intent-revealing methods on the nearest owner, e.g. order.ShipsTo(country), and leave fluent builders and query pipelines as they are.