Use the Template Design Pattern to Customize a Base Class Service in a Derived Class

Use the Template Design Pattern to Customize a Base Class Service in a Derived Class

n classic object-oriented design, a base class defines an interface in the form of public virtual member functions. A derived class may either override these functions or use them as-is. Often, this “all or nothing” policy is too coarse. What you really need is a more flexible design that allows a subclass to customize the functionality of a base class’s service without overriding it entirely. This article shows how to use the Template design pattern to achieve such flexibility.

How can a subclass redefine certain steps of an algorithm without changing the algorithm’s structure?

Use the Template design pattern.

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:

class Viewer{public: virtual int Open(const char * filename);//..};

Viewer::Open() 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:

class Viewer{public: int Open(const char * filename); //nonvirtual//..};

Open() 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.

In essence, the Template design pattern separates between mandatory primitive operations (which are implemented in the base class) and hooks for customized operations that a subclass may redefine later. Following these guidelines, the modified Viewer class now looks like this:

class Viewer{public: int Open(const char * filename) {  init();//mandatory steps: CRC check, authorization  if (is_custimized() )   customized_init();//allow customization by subclasses } virtual ~Viewer();protected: //hooks for a subclass's customizations virtual bool is_custimized()=0;  virtual void customized_init() {}private: //mandatory primitive operations void init();};

When a subclass overrides a protected member function, it changes that function’s access to private:

class MyViewer: public Viewer{//..private: bool is_custimized() const;  void customized_init(); }

The Well-Tempered Template
How does it work? A subclass that wishes to retain the base class’ functionality as-is shall provide a trivial implementation of is_customized():

bool is_customized() const {return false;)

When a subclass object calls Open(), it always executes Viewer::Open() because this function isn’t virtual. Open() in turn invokes init() to perform the mandatory primitive operations and then calls the subclass’ is_customized(). A subclass that wishes to customize the behavior of Open() must change the return value of is_customized() to true and override customized_init() as necessary.

A Template Recipe
Let’s summarize the design principles of a Template function:

  • A Template function is a nonvirtual public member function that performs a series of primitive operations.
  • Some of the primitive operations are mandatory; others may be customizable.
  • Mandatory operations are nonvirtual private members of the base class.
  • Customized primitive operations are declared virtual protected in the base class, but change their access to private in a subclass.
  • Customizable primitive operations that must be overridden are declared pure virtual in the base class; those that may be overridden are declared virtual.

In addition to the obvious benefit of providing a skeletal implementation in the base class while allowing a subclass to redefine certain portions thereof, the Template design pattern minimizes the number of primitive operations that a derived class must perform to flesh out the Template function. In this example, a derived class merely provides a trivial definition of is_customizable() to retain the base class functionality.


About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist