Have you ever called a method on an object… then chained another… and another… until the line looked like a trail of dots?
Or made a small internal change to one class… and suddenly had to update code across five other layers?
If yes, you’ve probably run into a violation of one of the most overlooked design principles in software engineering: The Law of Demeter (LoD).
Let’s understand it with a real-world example—and see why this principle matters more than you might think.
Imagine you're building a simple e-commerce system.
You have:
Customer
who owns a ShoppingCart
CartItems
CartItem
refers to a Product
Product
has a Price
Now let’s say you want to display the price of the first product in a customer’s shopping cart.
A common (but flawed) way to write this would be:
And a complete version might look like this:
This approach works, but it smells bad.
It’s what we call a “train wreck” or “dot-chaining”: one object reaching through several others to get what it wants.
You start with a Customer
, go into their ShoppingCart
, peek into its internal list of CartItems
, grab the first one, extract the Product
, and finally—get the Price
.
That’s a long dependency chain. And it’s fragile.
The OrderService
method is now tightly coupled to the entire internal structure of the customer and their cart.
ShoppingCart
changes how it stores items (e.g., using a Map
instead of a List
)CartItem
renames its getProduct()
methodProduct
evolves to store pricing in a new way…Boom. Your OrderService
code breaks. Even though it had nothing to do with those decisions.
You're reaching deep into object internals—violating encapsulation at multiple levels.
Customer
exposes its ShoppingCart
ShoppingCart
exposes its internal listCartItem
and Product
just to get a priceThis kind of reach-through breaks the principle of object-oriented design: objects should tell, not ask.
Imagine this change: You switch from using a Money
wrapper to a BigDecimal
for price representation in Product
.
Now, every part of your codebase that dot-chased its way to product.getPrice()
must be updated.
Your implementation detail leaked—and now you’re paying for it.
Testing displayFirstItemPrice()
becomes a mocking marathon.
To test it in isolation, you'd need to mock:
Customer
ShoppingCart
List
CartItem
Product
Price
One function. Six mocks. Exhausting.
“Only talk to your immediate friends.” — Law of Demeter
The Law of Demeter (also known as the Principle of Least Knowledge) says an object should only call methods on:
That’s it.
In plain terms: don't reach through one object to get to another.
Let’s rewrite this in a cleaner, more respectful way. We’ll push the responsibility down to the classes that know the most.
ShoppingCart
Customer
OrderService
Much better, right?
Now OrderService
doesn’t care about:
CartItem
containsProduct
holds priceIt just asks the Customer
for what it needs.
Yes, following LoD can result in additional small, delegating methods. But this “extra” code serves a critical purpose: it reduces coupling.
Think of it this way—would you rather write 3 lines now to isolate behavior, or refactor 300 later when your system breaks?
These small wrappers protect the rest of your codebase from internal changes. They enforce the principle of “Tell, Don’t Ask”—you tell an object what you want it to do, instead of reaching inside and doing it yourself.
Not at all. LoD doesn’t forbid getters.
Simple property access like customer.getName()
is perfectly fine—name
is a direct part of Customer
.
The issue arises with chained getters across object boundaries:
This creates tight coupling between the caller and the internal structure of several unrelated classes. Instead, you’re encouraged to delegate the operation to the object that owns the knowledge.
size()
on a list I get from an object?This is a nuanced area.
If getUsers()
returns a standard collection like List<User>
, then getUsers().size()
is generally acceptable. Lists are transparent and well-understood abstractions, and operations like size()
don’t break encapsulation.
However, this would be a violation:
The more layers of domain objects you traverse, the more you're violating LoD.The key is whether you're interacting with a simple data structure or delving into another object’s responsibility chain.
Like most principles, LoD is a guideline—not a hard rule. Some common exceptions include:
Map.get()
or List.size()
) is typically safe.The key is intentional design. If you understand the coupling trade-off and still find it justifiable—go for it. Just don’t do it by accident.
LoD works hand-in-hand with other key design principles:
OrderService
needs pricing logic, LoD nudges you to push it into ShoppingCart
or Customer
—where the relevant data already lives.In short: LoD is like a guardrail. It helps you avoid the slippery slope of exposing internals and tying your code together too tightly.