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.

Share the Post:
Heading photo, Metadata.

What is Metadata?

What is metadata? Well, It’s an odd concept to wrap your head around. Metadata is essentially the secondary layer of data that tracks details about the “regular” data. The regular

XDR solutions

The Benefits of Using XDR Solutions

Cybercriminals constantly adapt their strategies, developing newer, more powerful, and intelligent ways to attack your network. Since security professionals must innovate as well, more conventional endpoint detection solutions have evolved

AI is revolutionizing fraud detection

How AI is Revolutionizing Fraud Detection

Artificial intelligence – commonly known as AI – means a form of technology with multiple uses. As a result, it has become extremely valuable to a number of businesses across

AI innovation

Companies Leading AI Innovation in 2023

Artificial intelligence (AI) has been transforming industries and revolutionizing business operations. AI’s potential to enhance efficiency and productivity has become crucial to many businesses. As we move into 2023, several

data fivetran pricing

Fivetran Pricing Explained

One of the biggest trends of the 21st century is the massive surge in analytics. Analytics is the process of utilizing data to drive future decision-making. With so much of

kubernetes logging

Kubernetes Logging: What You Need to Know

Kubernetes from Google is one of the most popular open-source and free container management solutions made to make managing and deploying applications easier. It has a solid architecture that makes

ransomware cyber attack

Why Is Ransomware Such a Major Threat?

One of the most significant cyber threats faced by modern organizations is a ransomware attack. Ransomware attacks have grown in both sophistication and frequency over the past few years, forcing