The template pattern comes into play when you have several different use cases that are mostly the same but differ in just a few ways. Shared functionality and skeleton methods are defined in a base class, which will be overridden by each subclass. Each subclass provides a different implementation for the skeleton functions, and because all these objects all share a common interface we can use them interchangeably in other parts of our code.
Using class inheritance
The simplest way of implementing the template pattern is to define a base class that your various implementations inherit from and override. All of the methods we want included in the template subclasses must raise errors if not overridden. That way, if no implementation is present in a subclass we will see an error.
Take these printer classes for example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
Now if we do BogusPrinter.new.prepare_and_print(data)
we will see a “Not impelmented!” error because the expected print
method was not defined in the subclass. A quick and easy fix.
This gets the job done, but doesn’t seem ideal. What if we want to make a printer class that writes data to a PDF file? We might need this class to inherit from another class providing complex PDF logic.
1 2 3 4 5 |
|
Wrap the base template into a module
Ruby only has single inheritance, so there’s no way to subclass BasePrinter
and BadassPDFLibrary
. One way around the single-inheritance problem is to wrap up our BasePrinter
into a module to be included.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
|
Leveraging RSpec shared_examples
Using modules is a perfectly workable solution if we need to inherit from a different class. But there’s a third way we can enforce our expectations on the printer interface. That is to write some shared examples for this code that check for the presence of the expected methods.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
This approach goes beyond the error-raising we get from the module or base class and allows us to make stipulations about the return values from each template method. With a few tests like this, we can dispense with the need for abstract base methods.