Sonntag, 1. Februar 2015

Template method in Ruby!

Sharing business logic by inheritance is a common pattern in object oriented programming. But sometimes the piece of logic has very little variances, like:
class Bike
  def start
    "Check 2 tyres. Bike is started!"
  end
end

class Car
  def start
    "Check 4 tyres. Start gasoline engine. Car is started!"
  end
end
and:
Bike.new.start
=> "Check 2 tyres. Bike is started!"
Car.new.start
=> "Check 4 tyres. Start gasoline engine. Car is started!"
Quite static. Both classes seem to have something in common (they can be started and have a number of tyres to be checked before). But at the same point, they are specialized by the number of tyres and in having an engine to be started or none.
The template method pattern is a reasonable option to deal with such small variances. It is one of those patterns defined by the Gang of Four and fairly simple to implement.
The pattern is based on inheritance for the similarities (the template method), whereas the variances are separated by extracting them into appropriate methods, so called hook methods. The template method drives the bit that needs to vary, but it does so by making calls to abstract methods, which are then supplied by the concrete subclasses.
The template pattern is absolutely reasonable, when:
  1. inheritance is reasonable
  2. the logic equality is way more distinct than its variance
The example in a templated manner:
class Vehicle
  def start
    todos = ["Check #{tyres} tyres."]
    todos << "Start #{fuel} engine." if fuel
    todos << "#{self.class} is started!"
    todos.join(' ')
  end
private
  def tyres
    raise 'Called abstract method: tyres'
  end

  def fuel
    raise 'Called abstract method: fuel'
  end
end

class Car < Vehicle
private
  def tyres
    4
  end

  def fuel
    gasoline
  end
end

class Bike < Vehicle
private
  def tyres
    2
  end

  def fuel
    nil
  end
end
Since Ruby does not supply abstract methods, defining exception methods in the base class is common practice in Ruby world.
Even though the generalized start method (template method) now looks more complex, it dries out the repeated similarities and is way more flexible for further subclasses.
Please note the template method pattern only makes sense, when the algorithms are way more alike than different.
Even the refactored result can be improved. Abstract methods are the more static typed language way. It makes more sense for the base class Vehicle to simply supply a default implementation of these methods for the convenience of its subclasses:
class Vehicle
  def start
    todos = ["#{self.class} is started!"]
    todos.unshift "Start #{fuel} engine." if fuel
    todos.unshift "Check #{tyres} tyres." unless tyres.to_i.zero?
    todos.join(' ')
  end
private
  attr_reader :fuel, :tyres
end

class Car < Vehicle
private
  def tyres
    4
  end

  def fuel
    gasoline
  end
end

class Bike < Vehicle
  def tyres
    2
  end
end
and:
Bike.new.start
=> "Check 2 tyres. Bike is started!"
Car.new.start
=> "Check 4 tyres. Start gasoline engine. Car is started!"
Creating a new subclass can take advantage of the existing method structure required by the base class:
class Rocket < Vehicle
private
  def fuel
    "oxydizer"
  end
end
It works like a charm:
Rocket.new.start
=> "Start oxydizer engine. Rocket is started!"
Further articles of interest:

Supported by Ruby 2.1.1

Keine Kommentare:

Kommentar veröffentlichen