Nithin Bekal

About Subscribe Tweets Code Talks

The Law of Demeter

27 Jul 2014

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 foo itself (ie. any instance methods of foo)
  • 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
end

The 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
end

Now 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
end

This 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.

Home About Subscribe