Presenting the Problem
Consider a file viewer class that associates a file extension with its matching application. Normally, the viewer class provides some skeletal functionality, not just an interface:
virtual int Open(const char * filename);
recognizes common file extensions such as .htm .doc
, and .cpp
, and associates them with the Web browser, word processor, and C++ compiler installed on the target machine. However, certain file types require special handling. Compressed and encrypted files for example have to be decompressed and decrypted before their associated app can process them. In this case you want to modify Open()
slightly, but not override it entirely. After all, you don't want to reimplement the CRC and authorization validation code in every derived class. Another typical scenario is an unrecognized file extension. In this case too, you want to reuse certain primitive operations of the base class's service while adding specialized operations. What you need is a "pick and mix" design that will allow you to redefine certain steps in a subclass, without reimplementing Open()
. Obviously, the mundane virtual overriding mechanism can't offer this level of flexibility. Let's see how the Template design pattern solves this problem.
| Author's Note:The Template Design Pattern has nothing to do with C++ templates. The name comes from the original Smalltalk implementation of this Pattern and was later immortalized in the famous Gang of Four book. Similarly, the term "algorithm" used in this context refers to the implementation of an ordinary member function, not the generic algorithms of STL.
In the above example, Open() is declared virtual, implying that subclasses may override it. This is a common design mistake. If at least some parts of Viewer::Open() are needed in a subclass, this function shouldn't be virtual at all. This may sound counter-intuitive for those of you who were brought on classic object-oriented design books. However, you will shortly see that making this function nonvirtual is a necessary step towards a more flexible design.
The modified Open() now looks like this:
int Open(const char * filename); //nonvirtual
performs several primitive operations, some of which are mandatory: CRC and authorization validation, for example. These operations are accomplished by calling private member functions of class Viewer
. After completing the mandatory operations the base class allows its subclasses to customize certain primitive operations in Open()
. The customizable operations are declared as protected virtual member functions in the base class, and may be overridden in a subclass.