Ruby on Rails – Understanding the Law of Demeter

designdesign-patternsobject-orientedruby-on-rails

I am reading a book called Rails AntiPatterns and they talk about using delegation to to avoid breaking the Law of Demeter. Here is their prime example:

They believe that calling something like this in the controller is bad (and I agree)

@street = @invoice.customer.address.street

Their proposed solution is to do the following:

class Customer

    has_one :address
    belongs_to :invoice

    def street
        address.street
    end
end

class Invoice

    has_one :customer

    def customer_street
        customer.street
    end
end

@street = @invoice.customer_street

They are stating that since you only use one dot, you are not breaking the Law of Demeter here. I think this is incorrect, because you are still going through customer to go through address to get the invoice's street. I primarily got this idea from a blog post I read:

http://www.dan-manges.com/blog/37

In the blog post the prime example is

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

The blog post states that although there is only one dot customer.cash instead of customer.wallet.cash, this code still violates the Law of Demeter.

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.

EDIT

I completely understand and agree that this is still a violation and I need to create a method in Wallet called withdraw that handles the payment for me and that I should call that method inside the Customer class. What I don't get is that according to this process, my first example still violates the Law of Demeter because Invoice is still reaching directly into Customer to get the street.

Can somebody help me clear the confusion. I have been searching for the past 2 days trying to let this topic sink in, but it is still confusing.

Best Answer

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?

Related Topic