Click here to Skip to main content
15,867,568 members
Articles / Programming Languages / C++

Building Decorator Chains

Rate me:
Please Sign up or sign in to vote.
4.50/5 (4 votes)
22 Oct 2010CPOL3 min read 29.6K   136   23   5
The article explains a method, how flexible and extendible decorator chains can be built in a generic way. Its power is best seen if it is used with boost::factory and boost::bind.

Introduction

The decorator pattern is a classical design pattern in modern object-oriented development. Usually, it is used to extend the functionality of existing objects. In this article, I will introduce a simple method to build flexible and extendable chains of decorators using modern C++.

The method makes strong use of the boost::factory template. In combination with boost::bind and boost::ref, it is a powerful and easy to use tool for the creation of decorator chains.

The code I will show here is a basic idea of how to manage and create such chains. I have developed it for a specific application and I started to I love it. Nevertheless, it is just one possible way. If you have different approaches, please let me know. Of course, discussions are highly welcome.

Decorators

Decorator chains are usually built with constructor arguments of the decorator:

C++
#include <iostream>

using namespace std;

class abstract_class
{
public:
    virtual ~abstract_class( void ) { }
    virtual void do_something( void ) = 0;
};

class concrete_class : public abstract_class
{
public:
    void do_something( void ) { cout << "concrete_class_a\n"; }
};

class decorator : public abstract_class
{
    abstract_class *m_decorated_class;
public:
    decorator( abstract_class *decorated_class ) : 
		m_decorated_class( decorated_class ) { }
    void do_something( void )
    {
        m_decorated_class->do_something();
        cout << "decorator_a\n";
    }
};

int main( int argc , char **argv )
{
    concrete_class c;
    decorator d( &c );
    d.do_something();

    return 0;
}

Note that the component which should be decorated is passed as a pointer and that the destruction of this object is not handled by the decorator. Of course, it might be possible to pass the component as reference or to give the ownership of the component to the decorator. Nevertheless, you have to remember the head element, that is the decorator which is the last in the chain, and which is used to called do_something:

C++
int main( int argc , char **argv )
{
    concrete_class c;     // c is the head element
    decorator d1( &c );   // d1 is now the head element
    decorator d2( &d1 );  // d2 is now the head element
    decorator d3( &d2 );  // d3 is now the head element
    d3.do_something();    // use the head element to do_something

    return 0;
}

Remember the head element is simple in the above example. But it might become cumberstone in other applications. For example, if the decorator chain is hidden deep in some other classes, or if you have to construct the chain dynamically from some configuration file, etc. This is where decorator_chain comes into play.

Decorator Chain

The main idea is to encapsulate the creation and instantiation of the decorator objects as well as the component which is decorated into a single class:

C++
template< class Class , class DecoratorCategory = pointer_decorator_category >
class decorator_chain
{
public:

    typedef Class class_type;
    typedef DecoratorCategory category_type;

    decorator_chain( bool owner_of_class = false , bool owner_of_decorators = false )
    {
        // ...
    }

    ~decorator_chain( void )
    {
        // ...
    }

    template< class Factory >
    void create_class( Factory make_class = Factory() )
    {
        // ...
    }

    template< class Factory >
    void decorate( Factory make_class = Factory() )
    {
        // ...
    }

    class_type& get_class_reference( void ) { return *m_current; }
    const class_type& get_class_reference( void ) const { return *m_current; }

    class_type* get_class_pointer( void ) { return m_current; }
    const class_type* get_class_pointer( void ) const { return m_current; }
};

The class takes two templates arguments. One is the type of the component which should be decorated. This type can be abstract. The second argument indicates whether the objects which should be decorated are passed as pointers or as references to the decorator. The constructor has two arguments, which specify if the tail element is owned by the decorator_chain and if the decorators are owned by decorator_chain. If they are owned, decorator_chain will destroy these objects if the destructor of decorator_chain is called, otherwise not. The head (current) element can be accessed via get_class_reference or get_class_pointer.

The main work is done by create_class and decorate. The first method creates the tail element of the chain. It assumes that Factory is a function object, returning a new instance of class_type. This function object does not take any arguments. A simple example is:

C++
abstract_class* make_concrete_class( void )
{
    return new concrete_class;
}

The second method creates a decorator object. Here, Factory is a function object taking one argument - the component which should be decorated. A simple example for such a function is:

C++
abstract_class* make_decorator( abstract_class *ac )
{
    return new decorator( ac );
}

You might wonder about this strange way of calling new, but here the boost::factory template comes into play. It encapsulates a call of new, such that the above example can be written as:

C++
int main( int argc , char **argv )
{
    decorator_chain< abstract_class > db;
    db.create_class( boost::factory< concrete_class* >() );
    db.decorate( boost::bind( boost::factory< decorator* >() , _1 ) );
    db.decorate( boost::bind( boost::factory< decorator* >() , _1 ) );
    db.decorate( boost::bind( boost::factory< decorator* >() , _1 ) );
    db.get_class_reference().do_something();

    return 0;
}

Note, how easily you can build a function (taking the decorating state as argument) with the help of bind and the placeholder _1.

You can also use boost::bind for argument normalization, hence to pass values to the constructor. boost::ref can be used to pass references:

C++
class int_decorator : public abstract_class
{
    abstract_class *m_decorated_class;
    int m_val;
public:
    int_decorator( abstract_class *decorated_class , int val ) : 
	m_decorated_class( decorated_class ) , m_val( val ) { }
    void do_something( void )
    {
        m_decorated_class->do_something();
        cout << "int_decorator : " << m_val << "\n";
    }
};

class string_decorator : public abstract_class
{
    abstract_class *m_decorated_class;
    string &m_val;
public:
    string_decorator( abstract_class *decorated_class , string &val ) : 
		m_decorated_class( decorated_class ) , m_val( val ) { }
    void do_something( void )
    {
        m_decorated_class->do_something();
        cout << "string_decorator " << m_val << "\n";
    }
};

int main( int argc , char **argv )
{
    string str = "Hello world!";
    decorator_chain< abstract_class > dc;
    dc.create_class( boost::factory< concrete_class* >() );
    dc.decorate( boost::bind( boost::factory< decorator* >() , _1 ) );
    dc.decorate( boost::bind( boost::factory< int_decorator* >() , _1 , 10 ) );
    dc.decorate( boost::bind( boost::factory< string_decorator* >() , 
		_1 , boost::ref( str ) ) );
    dc.get_class_reference().do_something();

    return 0;
}

If you have to call the constructor of the decorator with the reference to the decorating object, you can set the second template argument of decorator_chain to reference_decorator_category.

Conclusions

In this article, I have shown a simple method to construct complex chains of decorators. It heavily uses boost::factory and boost::bind. Possible extensions could include:

  • Dynamic decorator chains. This requires that the decorators could be set separately, similar to decorator.set_decorated_state(decorated_state).
  • The chain could implement the abstract type, such that it behaves like the component.

If you have some interesting points, criticisms, or suggestions, please let me know.

How to Compile?

Compiling the examples is simple under Linux:

g++ -I/path/to/boost example1.cpp -o example1
# or
g++ -I/path/to/boost example2.cpp -o example2

where /path/to/boost points to directory of the boost libraries. You need at least version 1.43.

References

History

  • 20.10.2010 Initial version

License

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


Written By
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMemoryLeaks, imperfect container usage Pin
Andriy Padalka22-Feb-11 23:29
Andriy Padalka22-Feb-11 23:29 
GeneralRe: MemoryLeaks, imperfect container usage Pin
headmyshoulder23-Feb-11 7:49
headmyshoulder23-Feb-11 7:49 
GeneralRe: MemoryLeaks, imperfect container usage Pin
Andriy Padalka23-Feb-11 20:56
Andriy Padalka23-Feb-11 20:56 
GeneralRe: MemoryLeaks, imperfect container usage Pin
headmyshoulder23-Feb-11 21:09
headmyshoulder23-Feb-11 21:09 
GeneralRe: MemoryLeaks, imperfect container usage Pin
Andriy Padalka24-Feb-11 0:46
Andriy Padalka24-Feb-11 0:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.