http://www.dan-manges.com/blog/37
The Law of Demeter is not easy to understand when reading it for the first time. Quoting the definition from Wikipedia:
More formally, 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: 1. O itself 2. M's parameters 3. any objects created/instantiated within M 4. O's direct component objects In particular, an object should avoid invoking methods of a member object returned by another method.
The Wikipedia article summarizes it as "Only talk to your immediate friends." Developers seems to recognize violations by looking for more than one "dot", such as:
foo.bar.baz
Here, "foo" is talking to "baz" through "bar". The solution (in Ruby) is to use Forwardable and set up a delegation. Here is an example:
class Foo extend Forwardable def_delegator :bar, :baz def bar # an associated object # could be a belongs_to in Rails end end
With this delegation in place, we can reduce the two "dots" into one by doing: foo.baz. Forwardable and def_delegator take care of going from "foo" through "bar" to "baz." While this may seem to solve our problem, the solution has a significant misconception.
Delegation is an effective technique to avoid Law of Demeter violations, but only for behavior, not for attributes.
To explain, I need a better example than foo/bar/baz. One of the classic examples I have seen to explain this is a Paper Boy, a Customer, and a Wallet. A paper boy needs to collect money from his customers. A customer stores his/her money in a wallet. Here is how this might be modeled violating the Law of Demeter:
class Wallet attr_accessor :cash end class Customer has_one :wallet end class Paperboy def collect_money(customer, due_amount) if customer.wallet.cash < due_ammount raise InsufficientFundsError else customer.wallet.cash -= due_amount @collected_amount += due_amount end end end
Hopefully apparent from this example is that a paperboy should not be taking cash out of a customer's wallet. This is a clear Law of Demeter violation. Going back to the simple way to recognize this mistake, we can see two dots in "customer.wallet.cash". Here is how this might change with attribute delegation.
class Wallet attr_accessor :cash end class Customer has_one :wallet # attribute delegation def cash @wallet.cash end end class Paperboy def collect_money(customer, due_amount) if customer.cash < due_ammount raise InsufficientFundsError else customer.cash -= due_amount @collected_amount += due_amount end end end
This example is only slightly different. A customer now has cash, which simply delegates to cash in the wallet. Now in the Paperboy collect_money method, we don't have two dots, we just have one in "customer.cash". Has this delegation solved our problem? Not at all. If we look at the behavior, a paperboy is still reaching directly into a customer's wallet to get cash out. That's not good. However, if instead of delegating attributes, we delegate behavior, we will end up with a much better OO design.
class Wallet attr_accessor :cash def withdraw(amount) raise InsufficientFundsError if amount > cash cash -= amount amount end end class Customer has_one :wallet # behavior delegation def pay(amount) @wallet.withdraw(amount) end end class Paperboy def collect_money(customer, due_amount) @collected_amount += customer.pay(due_amount) end end
Again, the change is simple, but our OO-ness and class responsibilities are much better. Notice the way delegation is done now. The pay method on customer simply delegates to the withdraw method on the wallet. Even with the argument on the pay method, this could also be implemented by using Forwardable.
Is the Customer#pay method, with doing nothing but a simple delegation, valuable? Yes. We want our paper boy to know as little as possible about the customer. It's okay for the paper boy to know the customer has a method available to pay him. The paper boy knowing the customer has a wallet (and even further, that the wallet has cash), is not okay. This is what the Law of Demeter is about.
Thinking again about attribute/getter/setter delegation, it gives classes too much knowledge about other classes. This includes classes that are far away from each other in the domain model. Going back to the first example, we don't want a paper boy to know a customer has a wallet. Onto the second example, we really don't want a customer to know a wallet has cash if we don't need to. The behavior delegation is a much better way to solve this problem.
Because I'm not confident this example is perfectly clear (after all, why wouldn't a customer know he has cash, why should that be hidden (read: encapsulated) in the wallet?), here is another example, and what I think has caused some overuse of attribute delegations:
An Order belongs_to a Customer. Let's say we're developing a Rails view to display order information, and this should include details on the customer. We might write the view like this:
<%= @order.customer.name %>
Two dots! Demeter violation? No. If you thought it was, you might have a delegation in your model, something like:
class Order extend Forwardable def_delegator :customer, :name, :customer_name end
Our view now becomes:
<%= @order.customer_name %>
We've traded a dot for an underscore. And thinking about this further, why should an order have a customer_name? We're working with objects, an order should have a customer who has a name. Adding these attribute delegations also decreases maintainability.
The crux of this is that webpage views aren't domain objects and can't adhere to the Law of Demeter. Clearly from the examples of behavior delegation the Law of Demeter leads to cleaner code. However, when rendering a view, it's natural and expected that the view needs to branch out into the domain model. Also, anytime something in a view dictates code in models, take caution. Models should define business logic and be able to stand alone from views. If this "train-wreck" method calling in your views is bothersome, there is a better solution that I will blog about later.
Focus on delegating behavior more than attributes. This ties well into the "Tell, don't ask." principle. With the paper boy example, our code is much better when a customer tells his/her wallet to withdraw an amount, rather than asking the wallet for how much cash it has and then doing the logic in the customer model.