Thursday, 10 May 2007

Rails refactoring with delegate(..)

“When something is too hard it means that you’re not cheating enough.”
DHH Before:
1
2
3
4
5
6
7
8
9
10
11
class Cart
  def initialize
    @items    = []
  end

  def size   ;    @items.size    end
  def clear  ;    @items.clear   end
  def empty? ;    @items.empty?  end

  attr_reader :items
end

After

1
2
3
4
5
6
7
8
9
class Cart
  def initialize 
    @items    = []
  end

  delegate :clear, :empty?, :size, :to => :items

  attr_reader :items
end
What it does: When you call cart.size , Rails delegates the size() method call to the items object and executes: cart.items.size How it works: If you look at the source, you'll notice that Rails actually generates 3 methods equivalent to the code we have just removed (see 'Before'), and injects them in the class code. The behaviour does not change; it's just a shorter way to write this code. Should you want (be careful) to delegate ALL the unknown methods calls, you could write :
1
2
3
4
# delegate unknown methods calls to @items
def method_missing(method, *args, &block)
   @items.send method, *args, &block
end
API doc : http://caboo.se/doc/classes/Module.html#M003442

1 comments:

Dan said...

Refactoring with Delegates (C#)


Problem:
If multiple derived classes require slightly different versions of a method provided in the base class


Old Solution:
> Have the base method accept new parameters to execute different code within the base method
> However, this becomes unwieldy with function signatures like

protected double Calculate(data,someOtherflag,newflag,oldflag,forgottenMeaningFlag)
{
double dblReturnPrice = data.value;

if (someotherflag || newflag && olflag || forgottenMeaningFlag)
{
dblReturnPrice += 5;
}

return dblReturnPrice;

}


> and after all, you don't want derived-class specific code in a base class.

New Solution:
> Declare a delegate pointing to a function in the derived class

Base:
public Delegate double CalculateHandler(data)
Protected CalculateHandler PriceCalculator = null;

protected double Calculate(data)
{
double dblReturnPrice = data.value;
if (PriceCalculator!=null)
{
dblReturnPrice = PriceCalculator(data)
}

return dblReturnPrice;
}

Derived

base.PriceCalculator = new CalculateHandler(SpecificPriceCalculator)

public double SpecificPriceCalculator(data)
{
return data.value +5;
}