Decide Who Your Neighbors Are
The premise of this law is not to avoid "chaining" of function calls (or property/field access, etc.), but rather to limit the "reach" of one class of objects. In the case of the walking dog, the caller must know about the dog, a collective structure that contains all of the legs, and about an individual leg. This requires tight coupling between the consumer and the leg details, so it's better to let the dog handle its own legs.
Starting at the beginning
User Robert Bräutigam linked to the original paper in a comment to another answer. I thought it best to transcribe the actual text of the "law" here:
For all classes C, and for all methods M attached to C, all objects to which M sends a message must be instances of classes associated with the following classes:
- The argument classes of M (including C).
- The instance variable classes of C.
(Objects created by M, or by functions or methods which M calls, and objects in global variables are considers as arguments of M.)
K. Lieberherr, I. Holland, and A. Riel. 1988. Object-oriented programming: an objective sense of style. In Conference proceedings on Object-oriented programming systems, languages and applications (OOPSLA '88). Association for Computing Machinery, New York, NY, USA, 323–334. DOI:https://doi.org/10.1145/62083.62113
Classes, not Instances
To apply the law, look at the classes* that are known to the consuming class C
. If C
stores an instance of class A
, or can access an instance of A
through global/hierarchical scope, or accepts an instance of A
in a given method, then the damage is done. That method of C
already "knows about" A
, and depends on the contract it provides. That consumer method may call any method of any instance of A
, no matter where it came from (or how complex it was to find it).
This is what differentiates the dog example from, say, fluent APIs. in a fluent API, you may chain a dozen function calls together, but each return value tends to be an instance of the same class! Each successive result may be a new instance, but the point is that there is only one class. You're counting classes, not instances or consecutive invocations.
*Presumably, this applies to all manner of types including specific interfaces; you don't "know about" MySqlDbConnection
just because you know about DbConnection
.
The Spirit of the Law
Does the dog example break the Law of Demeter? We technically don't know, but it probably does. We don't have much information about the consuming context; does it "know about" dog legs because of some instance field we can't see? If so, the law permits the consumer to call any method of any leg.
Thus, the problem with the dog example isn't that the consumer accesses a deeply nested function (a direction for one particular among a set of legs on a dog), but that the consumer has to understand the concepts of a dog and of a set of legs and of individual legs. You don't fix it by hiding method calls, you fix it by decoupling from the classes that contain those methods.
The Law of Demeter doesn't reduce coupling so much as it it keeps coupling explicit. From there, you can more easily detect and address issues related to coupling.
What about Wallets?
A popular analogy (apparently created by David Bock) when talking about the Law of Demeter is that of a paper boy that is owed money by a customer. The paper boy doesn't ask for a wallet so that he can grab the money, he just asks for the money. Who cares if the customer keeps it in a wallet, and why should the paper boy have to get the money from it anyway?
Interestingly, if the paper boy has a wallet of his own, this does not break the Law of Demeter! The paper boy knows how to use a wallet, so he knows how to use the customer's wallet. The real lesson of this scenario is to assign responsibilities properly and to keep implementation details (such as wallet vs. money clip vs. wad of bills) private.
Tl;dr
Here are some possibilities for the name example, vis-à-vis the Law of Demeter, assuming account.user()
returns type User
.
- Maybe there is nothing wrong with
account.user().fullName()
! If the consumer does other work involving User
s, then the consumer has already "paid the price" of depending on the User
class contract.
- If it's not explicit now, then make it explicit! Work with the user directly - don't go through account. Instead of calling
account.user()
, have the consuming method explicitly depend on one User
instance as a parameter. Now, even if account.user()
suddenly returns a different class, that's somebody else's problem. The consumer can focus on working with a User
. Notably, this only "adds" coupling to User
for the consuming method in question, not the whole consuming class.
- You could create a dedicated interface for situations like this. The consumer doesn't rely on a the
User
, but only on some abstraction that carries a smaller contract than a full User
.