Your first example does not violate the law of Demeter. Yes, with the code as it stands, saying @invoice.customer_street
does happen to get the same value that a hypothetical @invoice.customer.address.street
would, but at each step of the traversal, the value returned is decided by the object being asked - it's not that "the paperboy reaches into the customer's wallet", it's that "the paperboy asks the customer for cash, and the customer happens to get the cash from their wallet".
When you say @invoice.customer.address.street
, you're assuming knowledge of customer and address internals - this is the bad thing. When you say @invoice.customer_street
, you are asking the invoice
, "hey, I'd like the customer's street, you decide how you get it". The customer then says to its address, "hey I'd like your street, you decide how you get it".
The thrust of Demeter is not 'you cannot ever know values from objects far away in the graph from you"; it is instead 'you yourself are not to traverse far along the object graph in order to obtain values'.
I agree this may seem like a subtle distinction, but consider this: in Demeter-compliant code, how much code needs to change when the internal representation of an address
changes? What about in non-Demeter-compliant code?
There are two ways I could code this:
The first way is to create getters (i.e. houses, streets, suburbs) and select / deselect recipients that way.: messageRecipients.streets.select(aStreet).
Don't think I prefer this way but I don't want to see the Law of Demeter misapplied.
Some think this is all LoD has to say:
the Law of Demeter for functions requires that a method m of an object O may only invoke the methods of the following kinds of objects:
- O itself
- m's parameters
- Any objects created/instantiated within m
- O's direct component objects
- A global variable, accessible by O, in the scope of m
In particular, an object should avoid invoking methods of a member object returned by another method. For many modern object oriented languages that use a dot as field identifier, the law can be stated simply as "use only one dot". That is, the code a.b.Method() breaks the law where a.Method() does not. As an analogy, when one wants a dog to walk, one does not command the dog's legs to walk directly; instead one commands the dog which then commands its own legs.
Wikipedia: Law of Demeter
This is a monument to structural thinking. There is not a single semantic or conceptual thought in any of that. A static analyzer could enforce this. Bleh. This is not all I think of when I think of the LoD.
The Law of Demeter is not a dot counting exercise.
When you are an object, it is better to only talk to your friends, not friends of friends. If you randomly pull in anything you happen to need you'll soon find you've pulled together so many things that any change to the code base is going to hurt you right here.
Friends are things whose interfaces you own. Only then are you promised that you could jump through these dots and always find the same thing. While that can happen it's not going to be true if you just randomly grab whatever you like out of a code base.
So if the client calling "messageRecipients.streets.select(aStreet)" owns the interface for messageRecipients
and streets
then it's talking to its friends and there is no LoD violation. This means the client owns all the interfaces it uses. They should not change without the client's permission. This should be made clear to maintenance programmers. This needs clear boundaries. But it's not about dot counting.
That's the real problem. Because of the way you're getting this other object, the dependence on it's interface isn't explicit. The dependence can be a surprise. If you respect that you'll find some way to make this dependence clear to other coders.
This shouldn't be some cobbled together situation. This should be a situation created by careful design. What's being created here actually has a name. It's called a DSL. If that's what you're making fine. If this is just some randomly thrown together stuff because you happened to need it then you're creating a problem waiting to happen.
The other name the LoD goes by is "the principle of least knowledge". It's asking you to understand that when you create these dot chains you're adding knowledge to your client not only of these interfaces but how they connect. The more you know the more it hurts when things change. Don't create chains no one promised would be stable.
The other way would be to hide the fact that MessageRecipients is using SelectableEntity at all and simple create 'proxy' methods for each method on SelectableEntity (i.e. selectHouse, deselectHouse, selectStreet, etc).
I like this idea, but i'd also hide whether what's being selected is a house, street, or suburb. Let the suburb be the only thing that cares that it's a suburb.
Best Answer
There are two reasons why the second is worse. The first, not really directly LOD-related, is that your walk logic isn't reusable, which is a problem in the case where there are multiple places in your codebase where a dog must walk.
The LOD-related reason why this code is bad is because it forces the current consumer to know that
DogLeg
exists and how to operate it. The unspoken expectation here is that your consumer only knows about aDog
, and that it can be made to move around, but how thatDog
moves around isn't something the consumer cares about (that's up to theDog
to manage for themselves).However, that is not necessarily the case for your other example.
If
Account
andUser
are both domain objects of which your consumer has public knowledge, then there's no issue with asking them to handle aUser
object directly.The expectation here is that "the account refers to its owner" is part of the
Account
interface, and therefore returning the owner (represented by aUser
object) is fair game.Comparatively, your
Dog
interface is not expected to include "the dog has legs", but rather "the dog is able to move around", and the legs are just an implementation detail so the dog is able to fulfill its contract (i.e. moving around). The interface itself doesn't specify the existence of legs, and therefore the consumer ofDog
shouldn't be relying on the existence of legs.In essence, a
DogLeg
is considered a private implementation detail, whereas aUser
(class) is publically known. This means that there's significantly less issue with expecting your consumer to handle aUser
than there is with expecting them to handle aDogLeg
.That being said, if
account.user()
was actually anAccountUser
object which would also be considered a private implementation detail, then the same principle applies as it does forDogLeg
.This is what makes LOD so tricky to pinpoint. It's not something that is objectively true based on your code alone, it hinges on subjective context and expectation of interfaces/contracts. By renaming the code, you change the reader's implicit expectation, which can change whether something is considered an LOD violation.
Technically, it's the same code. But what changes is our expectation of how acceptable it is to force a consumer to directly handler a
DogLeg
vs forcing them to handle aUser
.