Tuesday, June 12, 2007

Why Thinking in Ruby Matters

I was going through a great article by Jim Weirich where he discusses why it is important to think in Ruby, when you are programming in Ruby. He goes on to describe an implementation of the Adapter design pattern in Ruby and compares the implementation with a similar (but much scaled down) version in Java. Ruby gives you the power to express more in less words - conciseness of syntax, along with great metaprogramming abilities leads to the expressivity that Ruby offers.

Consider the Strategy design pattern, which allows encapsulation of an algorithm and allows users to vary them independent of the context. There is nothing object-oriented about a Strategy design pattern. It so happens that the GOF book describes it in the context of an object-oriented language, C++. Stated succinctly, the design pattern espouses yet another best practice of software design, separation of concerns, by decoupling the implementation of an algorithm from the context.

In Java, the Strategy design pattern is implemented through the composition of a polymorphic hierarchy. The strategy is composed into the context through an interface, which can support multiple implementations transparently. Here is an example from a Payroll application, where the algorithm for calculation of a salary is encapsulated into a strategy :


class SalaryComputation {
  // injected through dependency injection
  private SalaryComputationStrategy strategy;
  //..
  //..

  public BigDecimal compute(..) {
    //..
    //..
    return strategy.compute(..);
  }
  //..
}



Here SalaryComputationStrategy is an interface which can have multiple implementations. Typically in a Java project, the implementations are injected transparently through a DI container like Spring or Guice.


interface SalaryComputationStrategy {
  BigDecimal compute(..);
}

class DefaultSalaryComputationStrategy
  implements SalaryComputationStrategy {
    //.. implementation
}

class SpecialSalaryComputationStrategy
  implements SalaryComputationStrategy {
    //.. implementation
}



One of the immediate consequences of the above implementation in Java is the number of classes and the inheritance hierarchies involved in the implementation. Looks like an accidental complexity in implementation, but this is quite a normal way of dealing with a nail when all you have is a hammer.

How do you encapsulate an algorithm and ensure flexible swapping of implementations in a language like Ruby that offers powerful syntax extensibility ? Remember, Ruby is also object oriented, and the above Java implementation can be translated with almost no effort using equivalent Ruby syntaxes. And we have a Strategy implementation in Ruby. Can we do better ?

Abstraction Abstraction!

Abstraction is the key - when you program in a language, always choose the best form of abstraction that suits the problem you are modeling. In a strategy design pattern, all you are encapsulating is an algorithm, which is, by nature a functional artifact. The very fact that Java or C++ does not support higher order functions (anonymous inner classes and function pointers are for the destitutes) had forced us to use encapsulating objects as holders of algorithms. And that led to the plethora of class hierarchies in the Java implementation. Ruby supports higher order functions in the form of blocks and coroutine support in the form of yields - keep reading and hope for a better level of abstraction support.


class SalaryComputation
  def compute
    ## fetch basic, allowances
    @basic = ..
    @allowance = ..

    ## fixed logic goes here

    ## coroutine call for the specific algorithm
    yield(@basic, @allowance)
  end
  ##
end



and the algorithm can be injected inline by the client through a Ruby block :


SalaryComputation.new.compute {
  |b, a|
  tax = (b + a) * 0.2
  b + a - tax
}



and we do not have to define a single extra class for the strategy. The strategy is nicely embedded inline at the caller's site. Another client asking to use a different algorithm can supply a different block at her call site.

What if I want to use the same default algorithm for different clients, yet keeping the flexibility of plugging in multiple implementations ? DRY it up within a Ruby Module and use the power of Ruby mixins :


module DefaultStrategy
  def do_compute(basic, allowances)
    tax = basic * 0.3
    basic + allowances - tax
  end
end

class SalaryComputation
  include DefaultStrategy ## mixin

  def initialize basic
    @basic = basic
    @allowances = basic * 0.5
  end

  def compute
    do_compute(@basic, @hra)
  end
end



And for some places where I would like to inject a special strategy, define another Module for the same and extend the SalaryComputation class during runtime :


module SpecialStrategy
  def do_compute(basic, allowances)
    tax = basic * 0.2
    basic + allowances - tax
  end
end

s = SalaryComputation.new(100)
s.extend SpecialStrategy
puts s.compute



Handling Fine Grained Variations

Suppose a part of the algorithm is fixed, while the computation of the tax only varies. The typical way to handle this in Java will be through the Template Method pattern. When designing the same in Ruby, the mechanism melds nicely into the language syntax :


def compute
  @basic + @allowances - yield(@basic)
end



and the identity of the pattern disappears within the language. Ruby supports fine grained variations within algorithms through powerful language syntax, which can only be done using extra classes in Java.

And finally ..

Keep an eye on the Ruby open classes feature. You can rip open any class and make changes either at the class level or at a single instance level. This feature offers the most coarse grained way to implement strategies in Ruby.


## open up an existing class
class SalaryComputation

  ## alias for old method
  ## you may need it also
  alias :compute_old :compute

  def compute
    ## new strategy
  end
end



So, how many ways does Ruby offer to implement your Strategy Design Pattern ?

2 comments:

Antonio said...

Really, there should be a great way to achieve this combining dynamic mixins (i.e., mixins that are done at runtime) and some instance data. So you'll maybe instantiate your object with an @strategy or @type instance variable (or, in the case of a database, provide it), and that will trigger something like an instance-level include (using class << self) that includes the appropriate strategy. Add a simple strategy= method and encapsulate it all in one place.

Unknown said...

[there should be a great way to achieve this combining dynamic mixins]
Sure .. Ruby gives you multiple options of implementation for almost everything. Here is one with dynamic mixin :

class SalaryCompute
 def self.strategy *props
  props.each do |prop|
   self.class_eval <<-EOS
    include #{prop}
   EOS
  end
 end

 strategy :DefaultStrategy

 def initialize basic
  @basic = basic
  @hra = basic * 0.5
 end

 def compute
  do_compute(@basic, @hra)
 end
end

puts SalaryCompute.new(100).compute

And, in fact you can take the strategy method out of this class and have it somewhere as as a dynamic strategy method.