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

Design Patterns: Decorators – C++

0.00/5 (No votes)
22 Apr 2023CPOL2 min read 3.3K  
How to implement a compile-time Decorator
Design Patterns were always different in C++, and a usual use of these patterns in C++ might cause more complications and problems in this language. In this series of articles, I’ll show how to correctly implement them in C++. In this article, we’ll see how to implement a compile-time Decorator.

In the previous article, we saw a way to implement the Factory design pattern in C++. This time, I’ll present the way I see Decorators in C++ – at compile time.

Decorator Design Pattern

A decorator is actually an extension (or decorator) to an existing class. The classic example is an ice_cream class:

C++
class ice_cream {
public:
    ice_cream();

    /*... Access methods ...*/

private:
    std::vector<ice_cream_ball> balls;
};

Assume you want to create an ice_cream with lollipops, you can create a complete new class, with same ice_cream properties, and you can create a decorator ice_cream class:

C++
class ice_cream_interface {
public:
    virtual ~ice_cream_interface() = default;
    virtual void add_ball() = 0;
    virtual void remove_ball() = 0;
    virtual void print() = 0;
};

class ice_cream : public ice_cream_interface {
public:
    ice_cream();

    /*... Access methods ...*/
    void print() override {
        std::cout << "Ice cream with " << balls.size() << " balls.\n"; 
    }

private:
    std::vector<ice_cream_ball> balls;
};

class ice_cream_with_lollipop : public ice_cream_interface {
public:
    ice_cream_with_lollipop(ice_cream_interface* base_ice_cream)
                           : base_ice_cream(base_ice_cream) {}

    /*... Access methods ...*/
    void print() override {
        base_ice_cream->print();
        std::cout << "Lollipop type: " << lollipop_type << "\n";
    }

private:
    ice_cream_interface* base_ice_cream;
    std::string lollipop_type;
};

Well, this is one way to implement a Decorator design pattern, but it’s a run-time decorator, and here in C++, we can use the compiler power to implement it on compile time, and to extend its functionality.

Compile Time Decorator

Let’s start with the base class:

C++
class base {
public:
    explicit base() {}
    virtual ~base() = default;

    virtual void func() = 0;
};

Now let’s create a minimal inherited class which every decorator should inherit from, called base_core:

C++
class base_core : public base {
public:
    explicit base_core() {}
    virtual ~base_core() = default;

    void func() override {};
};

template <class T>
concept Decorator = std::is_base_of_v<base_core, T>; // Defining what is a 
                                                     // 'decorator' type

Now let’s define our first class (which can also be a decorator):

C++
template <Decorator D = base_core>
class basic_decorator : virtual public D {
public:
    void set_val(int val) { basic_decorator_val = val; }
    int get_val() { return basic_decorator_val; }

    void func() override {
        D::func();
        std::cout << "basic_decorator_val: " << basic_decorator_val << "\n";
    }

private:
    int basic_decorator_val;
};

Question: What if we want to decorate a class with more than one decorator?

Assume we defined some basic decorators as the one above: basic_decorator_1, basic_decorator_2, basic_decorator_3, basic_decorator_4. The allocation will look something like that:

C++
std::shared_ptr<base> b1 = std::make_shared<basic_decorator
<basic_decorator_1<basic_decorator_2<basic_decorator_3<basic_decorator_4<>>>>>>();

Looks a little bit messy and might not demonstrate a correct logic (because basic_decorator_3 not necessarily decorating basic_decorator_2, it might decorate basic_decorator as well). Let’s suggest a little fix:

C++
template <Decorator ...Decorators>
class advanced_decorator : virtual public Decorators... {
public:
    void set_val(int val) { advanced_decorator_val = val; }
    int get_val() { return advanced_decorator_val; }

    void func() override {
        (Decorators::func(), ...);
        std::cout << "advanced_decorator_val: " << advanced_decorator_val << "\n"
    }

private:
    int advanced_decorator_val;
};

But here, we got a little problem, what if we didn’t get any inheritance (a variadic template can be empty)? And still, we don’t want to force the developer to write every time the inheritance from base_core. Here is my solution:

C++
class empty_inheritance {};

/**
Structure to validate if an inheritance from variadic template 
contains at least one class
*/
template<typename Base = base_core, typename ...Decorators>
struct base_if_not_exists {
    static constexpr bool value = sizeof...(Decorators);
    using type = typename std::conditional<value, empty_inheritance, Base>::type;
};

And the usage:

C++
template <Decorator ...Decorators>
class advanced_core : virtual public base_if_not_exists
<base_core, Decorators...>::type, virtual public Decorators... { /* ... */ };

Accessing fields:

C++
void func() {
    std::shared_ptr<base> my_class = 
    std::make_shared<advanced_core<basic_decorator_1<>, basic_decorator_2<>>>();

    auto access_bd1 = dynamic_cast<basic_decorator_1<>*>(my_class.get());
    auto access_bd2 = dynamic_cast<basic_decorator_2<>*>(my_class.get());
    auto access_ad = dynamic_cast<advanced_core<basic_decorator_1<>, 
                     basic_decorator_2<>>*>(my_class.get());

    access_bd1->set_val(5);
    access_bd2->set_val(16);
    access_ad->set_val(55);
    access_ad->basic_decorator_2<>::set_val(70);

    my_class->func();
    /* Prints:
       basic_decorator_1_val: 5
       basic_decorator_2_val: 70
       advanced_decorator_val: 55
    */
}

Conclusion

In C++ ‘standard’ decorators implementations might not always be the best way to use them, and sometimes it’s better to invest more time to find the best fit for their implementation.

This was a simple example for a Decorator implementation at compile time. For a more complex example, see my repository: CppDecoratorDesignPattern.

License

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