Saturday, April 12, 2008

Template Method Design Pattern in Ruby

One of the Gang of Four Design Patterns that benefit quite a bit from static typing in Java is Template Method.

You have an abstract class like this:

public abstract class Template {
  public void templateMethod() {
    performStep1();
    performStep2();
    //do some extra work
  }
  protected abstract void performStep1();
  protected abstract void performStep2();
}

And then a concrete implementation:

public class Implementation extends Template {
  protected void performStep1() {
    //implementation goes here
  }
  public void performStep2() {
    //implementation goes here
  }
}

The nice thing with the Eclipse IDE is that the moment you make the Implementation class extend the abstract Template class, the Java Editor immediately notifies you of the missing implementations of the hook methods (performStep1() and performStep2()) so that you go ahead and implement them. In other words, having the step methods abstract provides us with a guiding API that helps developers figure out what to implement.

During development of Glimmer, I wanted to use the Template Method pattern for something. As I started to implement it, I remembered that there are no abstract methods in Ruby because you cannot have a compiler check that you implemented all abstract methods due to Ruby being an interpreted language.

When writing Ruby programs test-first however, not having a compiler is not a problem since compilation is really just a tiny part of proving that a program is correct. In reality, unit and integration tests are what truly give the confidence that a program works anyways. Also, while a compiler is useful in providing quick feedback about errors if a developer was testing the program functionally through the user-interface, tests provide feedback that is almost as fast, so with a language that requires much less keystrokes like Ruby, you end up with higher productivity gains in the end even without the benefit of compilation.

Here is how template method was implemented in Ruby:

class Template
  def template_method
    perform_step1;
    perform_step2;
    #do some extra work
  end
  def perform_step1
    raise "must be implemented by a class"
  end
  def perform_step2
    raise "must be implemented by a class"
  end
end

class Implementation < Template
  def perform_step1
    #implementation goes here
  end
  def perform_step2
    #implementation goes here
  end
end

So when writing a class that extends Template, if either of perform_step1 or perform_step2 was not implemented, you get an exception right away when running template_method. Of course, with test-driven development you have assertions, which will be better indicators of whether you implemented things correctly.

Still, in Ruby, you can have an alternative Template Method implementation by relying on blocks:

class TemplateImplementation
  def template_method(step1, step2)
    step1.call;
    step2.call;
    #do some extra work
  end
end

It is used as follows:

implementation = TemplateImplementation.new
implementation.template_method(
  lambda {print "Hello"},
  lambda {print "World"}
)

This way, you can pass the hook methods performStep1 and performStep2 as anonymous function blocks, which can be created in Ruby using the lambda keyword (a shortcut for Proc.new.) Lambda produces Proc objects, which can be easily executed by calling the call method on them.

In conclusion, while Template Method in Ruby misses the benefit of static typing, it can yield shorter more concise code, especially by relying on blocks, and it can still be implemented correctly by following test-driven development.

No comments: