Law of Demeter and the delegate method

Written by Richard. Filed under computing, rails. Tagged , , , . Bookmark the Permalink. Post a Comment. Leave a Trackback URL.

The Law of Demeter, or Principle of Least Knowledge is a fairly simple design pattern, which, simply put means that an object should only talk to it’s immediate “friends”

The law states that a method M of and object O may only invoke the methods of the following kind:

1. a method on O itself
2. any parameters passed to M
3. any objects instantiated within M
4. any direct components of O

The classic example coined by David Bock used a Paperboy (one object) delivering a paper, then extracting money from a Customer’s (another object) Wallet (and another):

  class Paperboy
    def get_payment(customer)
      self.money += customer.wallet.take_out(10)
    end
  end

In the “real world” the Paperboy would ask the customer for the money who would then take it out for them, rather then the Paperboy reaching into the customer’s back pocket and getting it for themself.

Really we want something as follows:

  class Paperboy
    def get_payment(customer)
      self.money += customer.get_payment(10)
    end
  end

  class Customer
    def get_payment(amount)
      wallet.take_out(10)
    end
  end

This may all seem trivial and a waste of time, but what happens if some Customers want to pay by cheque? Those decisions should have an impact on the Paperboy, otherwise we end up with:

  class Paperboy
    def get_payment(customer)
      if customer.pay_by_cash?
        self.money += customer.wallet.take_out(10)
      elsif customer.pay_by_cheque?
        self.money += customer.cheque_book.write_cheque(10)
      end
    end
  end

Where as it makes more sense for the change to be contained within the Customer:

  class Paperboy
    def get_payment(customer)
      self.money += customer.get_payment(10)
    end
  end

  class Customer
    def get_payment(amount)
      if pay_by_cash?
        wallet.take_out(10)
      elsif pay_by_cheque?
        cheque_book.write_cheque(10)
      end
    end
  end

So what does this have to do with Rails and the delegate method? The delegate method adds a quick and simple way of following the Law of Demeter without having to do very much at all.

  class Order
    belongs_to :customer
  end

  class Customer
    has_many :orders
    has_one :credit_card
    has_one :bank_account

    def payment_method
      if pay_by_card?
        credit_card
      elsif pay_by_account?
        bank_account
      end
    end
  end

  class CreditCard
    belongs_to :customer
  end

  class BankAccount
    belongs_to :customer
  end

This setup means to get an Order’s payment we would have to say:

  @order.customer.payment_method.withdraw(amount)

But if we simply change our objects as such:

  class Order
    belongs_to :customer
    delegate :withdraw_payment, :to => :customer
  end

  class Customer
    has_many :orders
    has_one :credit_card
    has_one :bank_account

    def withdraw_payment(amount)
      if pay_by_card?
        credit_card.withdraw(amount)
      elsif pay_by_account?
        bank_account.withdraw(amount)
      end
    end
  end

Now all we have to say is:

  @order.withdraw_payment(amount)

So at any time, the details of how a payment  is to be decided can be contained with the Customer. This is of course a simplistic example, but hopefully explains how you chould be using this handy feature.

4 Comments

  1. Posted October 25, 2009 at 6:51 pm | Permalink

    So to use withdraw_payment, you have to also create a withdraw method too in the Customer model right. not a big deal, but maybe you could add withdraw to your example, just to give the full story and show how the method jumps from one model to the next too.

    • Posted November 19, 2009 at 11:55 am | Permalink

      You don’t have to. I should have been a bit clearer, but what I’ve done is renamed withdraw into withdraw_payment, just so it makes a bit more sense in terms of the chain of calls. But withdraw_payment on the Customer object *can* be called withdraw:

      So the final call is:

        @order.withdraw(amount)

      In this case it’s now a bit unclear *what* I’m withdrawing.

      So Customer becomes:

        class Customer
          has_many :orders
          has_one :credit_card
          has_one :bank_account
          def withdraw(amount)
            if pay_by_card?
              credit_card.withdraw(amount)
            elsif pay_by_account?
              bank_account.withdraw(amount)
            end
          end
        end

      Or if there is only credit cards supported the chain can just be:

        class Customer
          has_many :orders
          has_one :credit_card
          delegate :withdraw, :to => :credit_card
        end

  2. Posted November 12, 2009 at 2:11 pm | Permalink

    “if pay_by_card”

    Where does the pay_by_card come from?

    • Posted November 19, 2009 at 11:52 am | Permalink

      Sorry, my bad. They are other methods on the Customer model which I didn’t provide implementations for. I should have provided them , but it’s a bad example in this case as the decision as to which payment method to use shouldn’t be done with ifs as I’m doing. Going by Open Closed principle, the decision making should be extensible.

3 Trackbacks

  1. [...] This post was mentioned on Twitter by Mark Needham, Richard Hart. Richard Hart said: Two blog posts in two days. What's going on? This time a little on the Law of Demeter http://bit.ly/YmUrd [...]

  2. By Coding: Pushing the logic back at Mark Needham on November 11, 2009 at 10:30 am

    [...] was reading a post on the law of demeter by Richard Hart recently and it reminded me that a lot of the refactorings that we typically do on code bases are [...]

  3. [...] [ via ur-ban.com] [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>