Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Please Don’t Fail Me! - Decorator Cries Out to Template Method

4.68/5 (13 votes)
13 Sep 2009CPOL6 min read 57.1K   145  
Are your classes that implement the Template Method Design Pattern Decorator aware?

Introduction

This article is a result of the serendipity experienced when implementing a small framework. The framework had a bunch of classes, which implemented some of the widely used design patterns. One of the classes implemented the Template Method pattern. The situation then was to make that class extensible without modifying it. The Decorator pattern seemed the right fit to extend (decorate) the class.

Besides providing a level of sophistication in code and the fun in its use, design patterns do not reveal until asked for the problems in mixing them. This article explains the problems when mixing Decorator and Template Method patterns. The article also discusses the possible solutions to circumvent the problem in mixing the above design patterns. Know why these patterns don't gel well? Please read on.

Assumptions

This article assumes knowledge in the following areas:

  1. Object Oriented Programming
  2. Design Patterns, especially Decorator and Template Method

Problem

Consider a Shape class like the one below, which is the base class for all Shapes, viz, Circle, Rectangle, Square, etc.

C++
class Shape
{
private: std::string _ type;

public: Shape(const std::string& type) : _ type (type)
        {
        }

public: std::string Type() const
        {
            return _type;
        }

protected: virtual void CreateDC() = 0;
protected: virtual void InitDC() = 0;
protected: virtual void Paint() = 0;
protected: virtual void ReleaseDC() = 0;

public: void Draw()
        {
            cout << "Drawing " << Type();

            CreateDC();
            InitDC();
            Paint();
            ReleaseDC();
        }
};

The Shape class listed above implements the template method design pattern via the Draw method. The custom shapes like Circle or Rectangle would have to implement the pure virtual functions – CreateDC, InitDC, Paint, ReleaseDC - in order to be drawn.

For instance, a Circle shape may be hypothetically implemented as follows:

C++
class Circle : public Shape
{
public: Circle() : Shape("Circle")
        {
        }

protected: void CreateDC()
           {
               cout << std::endl << "Circle::CreateDC";
           }

protected: void InitDC()
           {
               cout << std::endl << "Circle::InitDC";
           }

protected: void ReleaseDC()
           {
               cout << std::endl << "Circle::ReleaseDC";
           }

protected: void Paint()
           {
               cout << std::endl << "Circle::Paint";
           }
};

The typical usage of the above classes would be as follows:

C++
void main()
{
    //
    // Previous Code...
    //

    Shape* s = new Circle();
    s->Draw();

    //
    // Later Code...
    //
}

The output of the above program should be obvious.

As in the story of any application development, the Circle must be extensible without modifying the existing classes. For instance, the Circle may be filled; its border color and thickness may be changed, and so on. Let us say we want to fill the Circle. We have a couple of options:

  1. Derive a class from Circle, say FilledCircle and override the Paint method - This technique is not scalable, and cannot be used for filling other types of shapes. Besides, this is an approach which would result in a combinatorial explosion of classes depending on the extenders desired.
  2. Use a decorator, say ShapeFiller - This technique is extremely scalable since we can apply this to any Shape class.

Per the topic of the discussion, we choose Option #2.

Following is a hypothetical implementation of the ShapeFiller (Decorator) class:

C++
class ShapeFiller : public Shape
{
private: Shape* _shape;

public: ShapeFiller(Shape* shapeTobeFilled) : Shape(shapeTobeFilled->Name()),
           _shape(shapeTobeFilled)
        {
        }

protected: void CreateDC()
           {
              // CDC1: Call the underlying shape's CreateDC
              _shape->CreateDC();

              cout << std::endl << "ShapeFiller::CreateDC";
           }

protected: void InitDC()
           {
              // IDC1: Call the underlying shape's InitDC
              _shape->InitDC();

              cout << std::endl << "ShapeFiller::InitDC";
           }

protected: void ReleaseDC()
           {
              // RDC1: Call the underlying shape's ReleaseDC
              _shape->ReleaseDC();

              cout << std::endl << "ShapeFiller::ReleaseDC";
           }

protected: void Paint()
           {
              // Paint1: First call the underlying shape's Paint
              _shape->Paint();

              // Paint2: Custom logic to fill the shape (forget the color).
              // Do you see it filling?

              cout << std::endl << "ShapeFiller::Paint";
           }
};

There are several things to be observed in the above implementation:

  1. The above class will not compile, complaining the following error:
    error C2248: 'Shape::CreateDC' : 
    		cannot access protected member declared in class 'Shape'
    error C2248: 'Shape::InitDC' : 
    		cannot access protected member declared in class 'Shape'
    error C2248: 'Shape::ReleaseDC' : 
    		cannot access protected member declared in class 'Shape'
    error C2248: 'Shape::Paint' : 
    		cannot access protected member declared in class 'Shape'
  2. Although no custom logic is required in the CreateDC, InitDC and ReleaseDC methods of the ShapeFiller, they must have to be implemented simply as place holders forwarding calls to the underlying shape (needless those methods are pure virtual). The quickest remedy may seem to have default (empty) implementation for the above methods in the Shape class. Doing so would not be creating (init and release) the DC when using the ShapeFiller class. In other words, application will misbehave (probably crash) when attempting to Paint().
  3. The other (ugly) remedy may be making the protected methods - CreateDC, InitDC, ReleaseDC - public. It is a bad design choice. In the Object Oriented world, it is a crime.

So that is the problem - Attempting to extend a class that implements Template Method design pattern using Decorator design pattern results in a near-predicament situation.

Intent of Decorator Pattern [GoF]

  • Attach additional responsibilities to an object dynamically.
  • Decorators provide a flexible alternative to sub-classing for extending functionality.

Intent of Template Method Pattern [GoF]

  1. Define the skeleton of an algorithm in an operation, deferring some steps to subclasses.
  2. Template Method lets subclasses redefine certain steps of an algorithm without letting them to change the algorithm's structure.

As stated in the intent, the Decorator pattern avoids sub-classing while the Template Method pattern relies on it. Secondly, the Template Method need not publicize all the steps involved in an algorithm. On the other hand, the Decorator pattern relies on the public interface of the class it intends to decorate. Evidently, in our case, adopting the decorator pattern blows the purpose of the Template Method pattern.

Conclusion – Is there a Solution?

Yes and No.

YES

Approach 1: Make the class that uses the template method design pattern Decorator Pattern aware.

Consider the new Shape class:

C++
class Shape
{
friend class ShapeDecoratorBase; // <== Added friend declaration

private: std::string _type;

public: Shape(const std::string& type) : _type (type)
        {
        }

public: std::string Type() const
        {
           return _type;
        }

protected: virtual void CreateDC() = 0;
protected: virtual void InitDC() = 0;
protected: virtual void Paint() = 0;
protected: virtual void ReleaseDC() = 0;

public: void Draw()
        {
	       cout << "Drawing " << Type();

           CreateDC();
           InitDC();
           Paint();
           ReleaseDC();
        }
};

Instead of every individual decorator, bloat the code with redundant code that forwards calls to the underlying shape object, a common ShapeDecoratorBase base class is introduced. In other words, ShapeDecoratorBase truly implements the decorator pattern. This class is made a friend of the Shape class (Yes, a friend. Some readers might think that using friends is a bad design. But not really, follow the link "When to use friends"). The individual decorators may now derive from ShapeDecoratorBase and implement the Paint method to decorate the underlying shape.

The ShapeDecoratorBase class is defined as follows:

C++
class ShapeDecoratorBase : public Shape
{
protected: Shape* _shape;

public: ShapeDecoratorBase (Shape* shapeTobeFilled) : Shape(shapeTobeFilled->Name()),
           _shape(shapeTobeFilled)
        {
           cout << std::endl << "ShapeDecoratorBase Ctor";
        }

public: ~ShapeDecoratorBase()
        {
           cout << std::endl << "ShapeDecoratorBase Dtor";
        }

protected: void CreateDC()
           {
              cout << std::endl << "ShapeDecoratorBase::CreateDC";

              // CDC1: Call the underlying shape's CreateDC
              _shape->CreateDC();
           }

protected: void InitDC()
           {
              cout << std::endl << "ShapeDecoratorBase::InitDC";

              // IDC1: Call the underlying shape's InitDC
              _shape->InitDC();
           }

protected: void ReleaseDC()
           {
              cout << std::endl << "ShapeDecoratorBase::ReleaseDC";

              // RDC1: Call the underlying shape's ReleaseDC
              _shape->ReleaseDC();
           }

protected: void Paint()
           {
              cout << std::endl << "ShapeDecoratorBase::Paint";

              // Paint1: First call the underlying shape's Paint
              _shape->Paint();
           }
};

Here is the revised implementation of the ShapeFiller Decorator class:

C++
class ShapeFiller : public ShapeDecoratorBase
{
public: ShapeFiller(Shape* shapeTobeFilled) : ShapeDecoratorBase(shapeTobeFilled)
        {
           cout << std::endl << "ShapeFiller Ctor";
        }

public: ~ShapeFiller()
        {
           cout << std::endl << "ShapeFiller Dtor";
        }

protected: void Paint()
           {
              ShapeDecoratorBase::Paint();

              // Paint2: Custom logic to fill the shape (forget the color).
              // Do you see it filling?

              cout << std::endl << "ShapeFiller::Paint";
           }
};

The ShapeFiller class now works as expected. We can now have more classes like ShapeBorderThickener, etc. implemented similarly.

Approach 2: Using member function pointers to implement the template method design pattern. This technique is not discussed further since it is a far less elegant approach than Approach 1. In other words, it hurts the eyes of an object oriented programmer. However, the source code is attached for reference.

Downside

The major downside of these two approaches is that both require access to the source code of the base class (The Shape class in our case). This might not be the case in all situations.

If the framework implementer had not given forethought about this problem, there is no elegant solution.

Needless to say, the ShapeDecoratorBase class has to be provided by the framework designer and not by the user of the framework.

It must be understood that it is not just the Template Method class that should be made aware of the Decorator but it is the Programmer/Framework designer who must be aware of such scenarios. It is the framework designer who decides if a class should be made extensible or not.

NO

The situation cannot be resolved if the source of the base class is not accessible. If the template method class is NOT implemented to be Decorator aware, the Decorator Pattern cannot be used.

The other option, which is inheritance, has to be pursued that has its own set of problems.

The Final Word

We hope this article has brought to light a subtle and interesting issue when using the Decorator design pattern with the Template Method design pattern.

"When you implement a framework, give a thought if your classes that implement the Template method design pattern need to be made Decorator aware."

Happy designing!

History

  • 4-Sep-2009: Initial draft
  • 8-Sep-2009: Corrected typos per comment from UGoetzke
  • 9-Sep-2009: Added C# code sample
  • 13-Sep-2009: Rephrased the introduction

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)