I’m currently reading Design Patterns in Ruby by Russ Olsen, which is a book about implementing some of the Gang of Four’s design patterns in Ruby. I shall be summarising what I’ve read in a series of posts and providing some of my own examples. The purpose is mainly to improve my own understanding but I hope these blog posts are useful for some people. If you find these posts interesting then I highly recommend that you purchase a copy of Russ’s book.
The template pattern allows you to set up the outline of an algorithm and then modify that algorithm by overriding parts of it. The template design pattern is based on inheritance and it works by defining an abstract class, which outlines the algorithm. Subclassing the abstract class allows you to customise parts of the algorithm.
In this example I want to generate reports for different departments within a company, that have the same look and feel. The first step is to define an abstract class:
class BaseReport # Template algorithm def self.output report_title underline after_title report after_report end # Part of the algorithm that should be overridden by subclass def self.report_title raise NotImplementedError, 'Please specify a report title' end # Part of the algorithm that should be overridden by subclass def self.report raise NotImplementedError, 'Please define a report' end # After title hook def self.after_title end # After report hook def self.after_report end private def self.underline puts "----------" end end
The outline method defines the algorithm. At this point I need to introduce two new concepts; abstract methods and hooks.
As we know, Ruby does not have true abstract classes but we can replicate the idea of abstract classes by raising errors in the methods of the base class. In the example above, report and report_title are both abstract methods. They raise an error if they are not implemented in the subclass. By defining abstract methods you are forcing subclasses to implement their own logic for that part of the algorithm. So, in this example we are forcing subclasses to define their own report title and report contents.
Hooks are a way of allowing subclasses to add behaviour to an algorithm but this is purely optional. In the example above, after_title and after_hook are hook methods. These methods allow subclasses to add behaviour to the report generating algorithm either after the title or after the report.
If the hook methods are not overridden by the subclass then the subclass inherits the implementation from the super class – which does nothing.
After defining the abstract class, you are in a position to create the subclasses. In this example I’m going to create 3 different subclasses for the tech department, marketing department and finance department. Each of the subclasses must inherit from BaseReport and must implement report_title and report methods. TechReport and FinanceReport both additionally implement some of the hook methods.
class TechReport < BaseReport def self.report_title puts 'Tech Report' end def self.after_title puts 'In addition the the usual report we will have an update on tech debt' end def self.report puts 'Good stuff' end def self.after_report puts 'Tech debt has been reduced due to the use of the template pattern' end end class MarketingReport < BaseReport def self.report_title puts 'Marketing Report' end def self.report puts 'Bad month due to weather' end end class FinanceReport < BaseReport def self.report_title puts 'Financial Report' end def self.report puts 'Growing profits in all departments' end def self.after_report puts 'Forecast for next month: 32% growth' end end
As you can see, each subclass is able to implement it’s own specific behaviour whilst utilising the algorithm defined in the abstract class.
Finally, here is the result of our efforts:
TechReport.output #=> Tech Report #---------- #In addition the the usual report we will have an update on tech debt #Good stuff #Tech debt has been reduced due to the use of the template pattern MarketingReport.output #=> Marketing Report #---------- #Bad month due to weather FinanceReport.output #=> Financial Report #---------- #Growing profits in all departments #Forecast for next month: 32% growth
The main drawback of the template pattern, as far as I can see, is that it makes use of inheritance. Ruby developers tend not to use inheritance much and the gang of four authors themselves favour composition over inheritance.
The problem with inheritance is that your classes are tightly coupled to the parent class, as a posed to duck typing where the class only needs to implement the same interface. The other problem with inheritance is, whenever you want to add a new algorithm, you need to create a new subclass. With composition, you only need to create a new object that implements the interface.
The template method is a useful design pattern that allows you to define a template (abstract) algorithm and create numerous subclasses that vary the algorithm for their own specific need. The downside of this design pattern is that Ruby programmers tend to prefer composition over inheritance. My next post is all about the strategy pattern which achieves the same results as the template design pattern, using composition rather than inheritance.