The Wayback Machine - https://web.archive.org/web/20121019195248/http://www.codeguru.com/cpp/article.php/c17909/C-Tutorial-The-Template-Pattern.htm

C++ Tutorial: The Template Pattern

Introduction

This is a simple article on Template Pattern. Template Pattern is one of the widely used patterns. It allows us to set up the outline of an algorithm and leaves the details of the implementation later. Template Pattern is a behavior pattern. Let us have a look at the UML diagram below.

Template Pattern UML Diagram

TemplateMethod is a public non-virtual method while Operation is a protected pure-virtual method whose behaviour will be defined in the ConcreteClassA and ConcreteClassB. Even though TemplateMethod is a non-virtual method but due to its call to the Operation method, its behaviour is partially defined by Operation method in the polymorphic derived class.

A Simple Non-Templated Example

Let us look at the problem this pattern can solve. I have a TextOut base class which is meant to be used in the derived classes, say Console, Debug and File to print out the contents in console, debugger and file logging, respectively. The TextOut class has 10 virtual Print methods which I need to override in my derived classes. These Print method use the Box object as their parameters. The Box class is just a simple class whose constructor takes in a primitive data type(POD), and converts them to std::wstring.

class Textout
{
public:
	virtual void Print( 
		const wchar_t* fmt, 
		Box box1 );

	virtual void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2 );

	virtual void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3 );

	virtual void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, Box box4 );

	virtual void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, 
		Box box4, Box box5 );
	
	virtual void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, 
		Box box4, Box box5, Box box6 );
	
	virtual void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, Box box4, 
		Box box5, Box box6, Box box7 );

	virtual void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, Box box4, 
		Box box5, Box box6, Box box7, Box box8 );

	virtual void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, Box box4, 
		Box box5, Box box6, Box box7, Box box8, Box box9 );

	virtual void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, Box box4, Box box5, 
		Box box6, Box box7, Box box8, Box box9, Box box10 );
		
	std::wstring GetFormattedString();

	std::wstring StrReplace( 
		std::wstring& fmtstr, 
		const std::vector<std::wstring>& vs );

	std::wstring StrAnchor( size_t i );
};

These are 2 examples of what the Print methods are doing. I will not show the rest of them as they are similar in nature.

void Textout::Print( 
	const wchar_t* fmt, 
	Box box1 )
{
	std::wstring wsfmtstr = fmt;

	std::vector<std::wstring> vs;
	vs.push_back( box1.ToString() );

	m_str = StrReplace( wsfmtstr, vs );
}

void Textout::Print( 
	const wchar_t* fmt, 
	Box box1, Box box2 )
{
	std::wstring wsfmtstr = fmt;

	std::vector<std::wstring> vs;
	vs.push_back( box1.ToString() );
	vs.push_back( box2.ToString() );

	m_str = StrReplace( wsfmtstr, vs );
}

Below is the declaration of the Console class:

class Console : public Textout
{
public:
	virtual void Print( 
		const wchar_t* fmt, 
		Box box1 );

	virtual void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2 );

	virtual void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3 );

	virtual void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, Box box4 );

	virtual void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, 
		Box box4, Box box5 );
	
	virtual void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, 
		Box box4, Box box5, Box box6 );
	
	virtual void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, Box box4, 
		Box box5, Box box6, Box box7 );

	virtual void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, Box box4, 
		Box box5, Box box6, Box box7, Box box8 );

	virtual void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, Box box4, 
		Box box5, Box box6, Box box7, Box box8, Box box9 );

	virtual void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, Box box4, Box box5, 
		Box box6, Box box7, Box box8, Box box9, Box box10 );
};

Below is an example how I would override the Print methods without using template pattern:

void Console::Print( 
	const wchar_t* fmt, 
	Box box1 )
{
	Textout::Print( fmt, box1 );
	std::wcout << GetFormattedString();
}

void Console::Print( 
	const wchar_t* fmt, 
	Box box1, Box box2 )
{
	Textout::Print( fmt, box1, box2 );
	std::wcout << GetFormattedString();
}

Remember I have to repeat this boilerplated code 8 more times. Here is an example on how to call the Console::Print method.

#include "Console.h"

int _tmain(int argc, _TCHAR* argv[])
{
	Console console;
	console.Print(L"{0} prints {1}\n", L"Console", 123);

	return 0;
}


C++ Tutorial: The Template Pattern

A Simple Templated Fix

Using the template pattern, I can just define this above code just once. Below is an abstract Textout class to take advantage of Template Pattern.

class AbstractTextout
{
public:
	void Print( 
		const wchar_t* fmt, 
		Box box1 );

	void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2 );

	void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3 );

	void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, Box box4 );

	void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, 
		Box box4, Box box5 );
	
	void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, 
		Box box4, Box box5, Box box6 );
	
	void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, Box box4, 
		Box box5, Box box6, Box box7 );

	void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, Box box4, 
		Box box5, Box box6, Box box7, Box box8 );

	void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, Box box4, 
		Box box5, Box box6, Box box7, Box box8, Box box9 );

	void Print( 
		const wchar_t* fmt, 
		Box box1, Box box2, Box box3, Box box4, Box box5, 
		Box box6, Box box7, Box box8, Box box9, Box box10 );

	std::wstring StrReplace( 
		std::wstring& fmtstr, 
		const std::vector<std::wstring>& vs );

	std::wstring StrAnchor( size_t i );

protected:
	virtual void Process(const std::wstring& str) = 0;
};

All the Print methods in this AbstractTextout are changed to non-virtual. I have removed the GetFormattedString method and added a pure-virtual Process method and the Print methods will call this Process method which I need to implement in the derived class. Below are the 2 new Print method definitions. Notice there is little changes, except it calls the pure-virtual Process method with the formatted string.

void AbstractTextout::Print( 
	const wchar_t* fmt, 
	Box box1 )
{
	std::wstring wsfmtstr = fmt;

	std::vector<std::wstring> vs;
	vs.push_back( box1.ToString() );

	std::wstring str = StrReplace( wsfmtstr, vs );

	Process(str);
}

void AbstractTextout::Print( 
	const wchar_t* fmt, 
	Box box1, Box box2 )
{
	std::wstring wsfmtstr = fmt;

	std::vector<std::wstring> vs;
	vs.push_back( box1.ToString() );
	vs.push_back( box2.ToString() );

	std::wstring str = StrReplace( wsfmtstr, vs );

	Process(str);
}

Below is the new Console class declaration. We need to implement the Process method!

class Console :
	public AbstractTextout
{
public:
	Console(void);
	virtual ~Console(void);
protected:
	void Process(const std::wstring& str);

};

Below are the definition of Console's constructor, destructor and its only method (which is Process).

Console::Console(void)
{
}

Console::~Console(void)
{
}

void Console::Process(const std::wstring& str)
{
	std::wcout << str;
}

You can read the source code of the Debug and File class if you are interested.

// Debug class declaration
class Debug :
	public AbstractTextout
{
public:
	Debug(void);
	virtual ~Debug(void);
protected:
	void Process(const std::wstring& str);

};

// Debug class definition
Debug::Debug(void)
{
}

Debug::~Debug(void)
{
}

void Debug::Process(const std::wstring& str)
{
#ifdef _DEBUG
	OutputDebugStringW( str.c_str() );
#endif
}

// File class declaration
class File :
	public AbstractTextout
{
public:
	File(void);
	virtual ~File(void);
	bool Open(wchar_t* file, wchar_t* args);
	void Close();

	bool IsOpen() { return !(fp == NULL); }
protected:
	void Process(const std::wstring& str);
private:
	FILE* fp;
};

// File class definition
File::File(void)
: fp(NULL)
{
}

File::~File(void)
{
	Close();
}

void File::Process(const std::wstring& str)
{
	if(fp)
		fwprintf_s(fp, L"%s", str.c_str());
}

bool File::Open(wchar_t* file, wchar_t* args)
{
	_wfopen_s(&fp, file, args);
	
	if(fp==NULL)
		return false;
		
	return true;
}

void File::Close()
{
	if(fp)
	{
		fclose(fp);
		fp = NULL;
	}
}

Here is an example on how to call these classes' Print method. As you may have noticed, the way to call Console::Print method has not changed!

#include "Console.h"
#include "Debug.h"
#include "File.h"

int _tmain(int argc, _TCHAR* argv[])
{
	Console console;
	console.Print(L"{0} prints {1}\n", L"Console", 123);

	Debug debug;
	debug.Print(L"{0} prints {1}\n", L"Debug", 123);

	File file;
	file.Open(L"text.txt", L"wt");
	file.Print(L"{0} prints {1}\n", L"File", 123);
	file.Close();
	return 0;
}

Conclusion

We have looked an example of how Template Pattern is applied to a problem. This is a rather unique problem which is solved by Template Pattern. For most of time, we use Template Pattern to define the behaviour of our class later. Thank you for reading!

Reference

Design Patterns: Elements of Reusable Object-Oriented Software

About the Author

Wong Shao Voon

I guess I'll write here what I does in my free time, than to write an accolade of skills which I currently possess. I believe the things I does in my free time, say more about me.

When I am not working, I like to watch Japanese anime. I am also writing some movie script, hoping to see my own movie on the big screen one day.

I like to jog because it makes me feel good, having done something meaningful in the morning before the day starts.

I also writes articles for CodeGuru; I have a few ideas to write about but never get around writing because of hectic schedule.

Downloads

IT Offers

Comments

  • There are no comments yet. Be the first to comment!

Whitepapers and More

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds