..

Law of Demeter and the delegate method

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.