The law of Demeter
Reducing the dependencies between classes is a good way to make your code more flexible. The Law of Demeter is a technique that helps with this and reduces coupling between objects. Let’s see how you can apply this to your Ruby code (but remember that these rules apply to any object oriented code).
The Law of Demeter states that when you call a method bar(x, y) on an object foo, it should only interact with objects that are closely related to it. The kinds of objects on which bar can invoke methods are:
- the object
fooitself (ie. any instance methods offoo) - the arguments of the method
bar(ie. x and y) - any objects instantiated inside the method
bar - attributes (instance variables) of
foo
Here, calling x.foobar() or @some_attr.foobar() is OK, but x.baz.foobar() and @some_attr.bar.foobar() are not.
The Paperboy and Wallet Metaphor
The metaphor of a paper boy collecting money from a customer (described in this paper) is an excellent way to understand the Law of Demeter. Imagine a Paperboy class that must collect money from a Customer:
class Paperboy
def collect_money(customer, amount)
if customer.wallet.cash > amount
customer.wallet.withdraw(amount)
else
# collect_later
end
end
endThe Paperboy is relying on the knowledge that a customer has a wallet and that it has cash. What happens now if Wallet changes its interface and uses the name balance instead of cash? Or adds a bang (!) to the #withdraw method to indicate that it changes the state of the wallet?
To avoid this, we could make the Customer responsible for paying, while the Paperboy only knows that Customer has a #pay_amount method.
class Customer
def pay_amount(amount)
@wallet.withdraw!(amount)
end
end
class Wallet
def withdraw!(amount)
return false if @balance < amount
@balance -= amount
true
end
endNow the Paperboy can simply call #pay_amount on Customer and not have to worry if the money is coming out of a wallet. The customer could be paying with the loose change in their pocket, without reaching for the wallet, and the paper boy doesn’t need to know about that detail.
class Paperboy
def collect_money(customer, amount)
if customer.pay_amount(amount)
puts 'Thanks!'
else
# collect_later
end
end
endThis isolates the Paperboy from any changes in Wallet. If you were to change the internals of Wallet, it would not have any effect on the other classes. Even if you were to change the interface to #withdraw, the only change needed would be in Customer.
Related articles
- The Paperboy, the Wallet and the Law of Demeter [PDF]
- Practical Object Oriented Design in Ruby: Interfaces, by Sandi Metz
- Demeter - It’s not just a good idea. It’s the law - article by Avdi Grimm