Jump to content

The ultimate community for Ruby on Rails developers.


Photo

Delegate Law of Demeter confusion


  • Please log in to reply
2 replies to this topic

#1 sidneyquitorio8

sidneyquitorio8

    Passenger

  • Members
  • 1 posts

Posted 17 October 2013 - 08:58 AM

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_stree
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:
 
 
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.

 

 

 
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.


#2 Ohm

Ohm

    Guard

  • Members
  • 179 posts
  • LocationCopenhagen

Posted 17 October 2013 - 06:43 PM

This is up the alley of the "Tell, don't ask" pattern http://robots.though...6/tell-dont-ask

 

Personally, with your first example, I'd do something like

class Customer
  has_one :address
  belongs_to :invoice

  delegate :street, to: :address
end
  
class Invoice
  has_one :customer
  
  delegate :street, to: :customer  
end
    
@street = @invoice.street

The second example, I'd do something like

class Wallet
  attr_accessor :cash
end

class Customer
  has_one :wallet
  
  delegate :cash, to: :wallet  

  def collect_money(amount)
    raise InsufficientFundsError if cash < amount
    cash -= amount
    amount
  end
end
    
class Paperboy
  def collect_money(customer, due_amount)
    @collected_amount += customer.collect_money(due_amount)
  end
end

Blog: http://ohm.sh | Twitter: madsohm


#3 specious

specious

    Passenger

  • Members
  • 3 posts

Posted 19 October 2013 - 06:58 AM

The Law of Demeter is a guideline for doing software design, not a stone tablet. Most of the time it is correct, but there are instances where it is inappropriate or overkill. Sometimes you are dealing with third party libraries that you can't change, for example.

 

It is more useful as a tool to make you think about the level of coupling in your code in relation to the overall design. While just counting the number of periods in the train-wreck method call is a neat way of quantifying it, perhaps if you are writing some software that analyses code to pop up thought bubbles in your IDE, it isn't a panacea.

 

In the example above, it should make you think about the object responsibilities and why you want to ask an invoice for such a low-level detail as one line of the address. Why not ask it to format the invoice address for printing, or give it a method that sends the invoice as an email.

 

In the paperboy example, maybe the Paperboy ought to call the obtain_payment method on Customer, passing an invoice. The Customer can then call get_total on the Invoice, look in his own Wallet to see if he has enough cash, and if not he can opt to pay the Papershop directly with his DebitCard or give the money to the Paperboy, possibly recording the payment in some kind of Account at the same time.

 

The point here is not about the number of dots - it is about how much the Paperboy object needs to know about internal implementation of the Customer. He doesn't care if he has a wallet, purse or bank account, he just wants his money. This allows you to change the implementation of the get_payment method without touching the code in Paperboy.

 

Thinking about the level of abstraction in your methods, are they high-level, mid-level, or real low-level nitty-gritty stuff? If you find a method that's calling a method that's significantly above or below it's peer group, then you should be asking yourself questions about why. For example the do_the_gardening method on the CEO object has a different implementation from the one in the Gardener object. We wouldn't expect the CEO to be interacting with the Spade object directly  :)


  • james likes this




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users