DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Design Pattern: What You Need to Know to Improve Your Code Effectively
  • Prototype Pattern in JavaScript
  • Object Relational Behavioral Design Patterns in Java
  • Distribution Design Patterns in Java - Data Transfer Object (DTO) And Remote Facade Design Patterns

Trending

  • Rethinking Recruitment: A Journey Through Hiring Practices
  • While Performing Dependency Selection, I Avoid the Loss Of Sleep From Node.js Libraries' Dangers
  • Transforming AI-Driven Data Analytics with DeepSeek: A New Era of Intelligent Insights
  • A Modern Stack for Building Scalable Systems
  1. DZone
  2. Coding
  3. Languages
  4. Decorator Design Pattern in Modern C++

Decorator Design Pattern in Modern C++

To facilitates the additional functionality to objects.

By 
Vishal Chovatiya user avatar
Vishal Chovatiya
·
Sep. 17, 20 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
15.8K Views

Join the DZone community and get the full member experience.

Join For Free

In software engineering, Structural Design Patterns deal with the relationship between objects and classes i.e. how objects and classes interact or build a relationship in a manner suitable to the situation. The Structural Design Patterns simplify the structure by identifying relationships. In this article, we're going to take a look at the not so complex yet subtle design pattern that is Decorator Design Pattern in Modern C++ due to its extensibility and testability. It is also known as a Wrapper.

The code snippets you see throughout this series of articles are simplified, not sophisticated. So you often see me not using keywords like override, final, public(while inheritance) just to make code compact and consumable(most of the time) in single standard screen size. I also prefer struct instead of class just to save line by not writing "public:" sometimes and also miss virtual destructor, constructor, copy constructor, prefix std::, deleting dynamic memory, intentionally. I also consider myself a pragmatic person who wants to convey an idea in the simplest way possible rather than the standard way or using jargon.

Note:

If you stumbled here directly, then I would suggest you go through What is design pattern? first, even if it is trivial. I believe it will encourage you to explore more on this topic.

All of this code you encounter in this series of articles are compiled using C++20(though I have used Modern C++ features up to C++17 in most cases). So if you don't have access to the latest compiler you can use https://wandbox.org/ which has preinstalled boost library as well.

Intent

To facilitates the additional functionality to objects.

Sometimes we have to augment the functionality of existing objects without rewrite or altering existing code, just to stick to the Open-Closed Principle. This also preserves the Single Responsibility Principle to have extra functionality on the side.

Decorator Design Pattern Examples in C++

And to achieve this we have two different variants of Decorator Design Pattern in C++:
  1.  Dynamic Decorator: Aggregate the decorated object by reference or pointer.
  2.  Static Decorator: Inherit from the decorated object.

Dynamic Decorator

C++
xxxxxxxxxx
1
26
 
1
struct Shape {
2
    virtual operator string() = 0;
3
};
4
5
struct Circle : Shape {
6
    float   m_radius;
7
8
    Circle(const float radius = 0) : m_radius{radius} {}
9
    void resize(float factor) { m_radius *= factor; }
10
    operator string() {
11
        ostringstream oss;
12
        oss << "A circle of radius " << m_radius;
13
        return oss.str();
14
    }
15
};
16
17
struct Square : Shape {
18
    float   m_side;
19
20
    Square(const float side = 0) : m_side{side} {}
21
    operator string() {
22
        ostringstream oss;
23
        oss << "A square of side " << m_side;
24
        return oss.str();
25
    }
26
};


So, we have a hierarchy of two different Shapes (i.e. Square & Circle), and we want to enhance this hierarchy by adding color to it. Now we're suddenly not going to create two other classes e.g. colored circle and a colored square. That would be too much and ultimately isn't a scalable option.

Rather we can just have ColoredShape as follows.

C++
xxxxxxxxxx
1
22
 
1
struct ColoredShape : Shape {
2
    const Shape&    m_shape;
3
    string          m_color;
4
5
    ColoredShape(const Shape &s, const string &c) : m_shape{s}, m_color{c} {}
6
    operator string() {
7
        ostringstream oss;
8
        oss << string(const_cast<Shape&>(m_shape)) << " has the color " << m_color;
9
        return oss.str();
10
    }
11
};
12
13
// we are not changing the base class of existing objects
14
// cannot make, e.g., ColoredSquare, ColoredCircle, etc.
15
16
int main() {
17
    Square square{5};
18
    ColoredShape green_square{square, "green"};    
19
    cout << string(square) << endl << string(green_square) << endl;
20
    // green_circle.resize(2); // Not available
21
    return EXIT_SUCCESS;
22
}


Why this is a dynamic decorator?
Because you can instantiate the ColoredShape at runtime by providing needed arguments. In other words, you can decide at runtime that which Shape(i.e. Circle or Square) is going to be coloured.

You can even mix the decorators as follows:

C++
xxxxxxxxxx
1
20
 
1
struct TransparentShape : Shape {
2
    const Shape&    m_shape;
3
    uint8_t         m_transparency;
4
5
    TransparentShape(const Shape& s, const uint8_t t) : m_shape{s}, m_transparency{t} {}
6
7
    operator string() {
8
        ostringstream oss;
9
        oss << string(const_cast<Shape&>(m_shape)) << " has "
10
            << static_cast<float>(m_transparency) / 255.f * 100.f
11
            << "% transparency";
12
        return oss.str();
13
    }
14
};
15
16
int main() {
17
    TransparentShape TransparentShape{ColoredShape{Square{5}, "green"}, 51};
18
    cout << string(TransparentShape) << endl;
19
    return EXIT_SUCCESS;
20
}

Limitation of Dynamic Decorator

If you look at the definition of Circle, You can see that the circle has a method called resize(). we can not use this method as we did aggregation on-base interface Shape & bound by the only method exposed in it.

Static Decorator

The dynamic decorator is great if you don't know which object you are going to decorate and you want to be able to pick them at runtime but sometimes you know the decorator you want at compile time in which case you can use a combination of C++ templates & inheritance.

C++
xxxxxxxxxx
1
43
 
1
template <class T>  // Note: `class`, not typename
2
struct ColoredShape : T {
3
    static_assert(is_base_of<Shape, T>::value, "Invalid template argument"); // Compile time safety
4
5
    string      m_color;
6
7
    template <typename... Args>
8
    ColoredShape(const string &c, Args &&... args) : m_color(c), T(std::forward<Args>(args)...) { }
9
10
    operator string() {
11
        ostringstream oss;
12
        oss << T::operator string() << " has the color " << m_color;
13
        return oss.str();
14
    }
15
};
16
17
template <typename T>
18
struct TransparentShape : T {
19
    uint8_t     m_transparency;
20
21
    template <typename... Args>
22
    TransparentShape(const uint8_t t, Args... args) : m_transparency{t}, T(std::forward<Args>(args)...) { }
23
24
    operator string() {
25
        ostringstream oss;
26
        oss << T::operator string() << " has "
27
            << static_cast<float>(m_transparency) / 255.f * 100.f
28
            << "% transparency";
29
        return oss.str();
30
    }
31
};
32
33
int main() {
34
    ColoredShape<Circle> green_circle{"green", 5};
35
    green_circle.resize(2);
36
    cout << string(green_circle) << endl;
37
38
    // Mixing decorators
39
    TransparentShape<ColoredShape<Circle>> green_trans_circle{51, "green", 5};
40
    green_trans_circle.resize(2);
41
    cout << string(green_trans_circle) << endl;
42
    return EXIT_SUCCESS;
43
}


As you can see we can now call the resize() method which was the limitation of Dynamic Decorator. You can even mix the decorators as we did earlier.

So essentially what this example demonstrates is that if you're prepared to give up on the dynamic composition nature of the decorator and if you're prepared to define all the decorators at compile time you get the added benefit of using inheritance.

And that way you actually get the members of whatever object you are decorating being accessible through the decorator and mixed decorator.

Functional Approach to Decorator Design Pattern Using Modern C++

Up until now, we were talking about the Decorator Design Pattern which decorates over a class but you can do the same for functions. Following is a typical logger example for the same:

C++
xxxxxxxxxx
1
32
 
1
// Need partial specialization for this to work
2
template <typename T>
3
struct Logger;
4
5
// Return type and argument list
6
template <typename R, typename... Args>
7
struct Logger<R(Args...)> {
8
    function<R(Args...)>    m_func;
9
    string                  m_name;
10
11
    Logger(function<R(Args...)> f, const string &n) : m_func{f}, m_name{n} { }
12
 
13
    R operator()(Args... args) {
14
        cout << "Entering " << m_name << endl;
15
        R result = m_func(args...);
16
        cout << "Exiting " << m_name << endl;
17
        return result;
18
    }
19
};
20
21
template <typename R, typename... Args>
22
auto make_logger(R (*func)(Args...), const string &name) {
23
    return Logger<R(Args...)>(std::function<R(Args...)>(func), name);
24
}
25
26
double add(double a, double b) { return a + b; }
27
28
int main() {
29
    auto logged_add = make_logger(add, "Add");
30
    auto result = logged_add(2, 3);
31
    return EXIT_SUCCESS;
32
}


Above example may seem a bit complex to you but if you have a clear understanding of variadic temple then it won't take more than 30 seconds to understand what's going on here.

Benefits of Decorator Design Pattern

  1.  Decorator facilitates augmentation of the functionality for an existing object at run-time & compile time.
  2.  Decorator also provides flexibility for adding any number of decorators, in any order & mixing it.
  3.  Decorators are a nice solution to permutation issues because you can wrap a component with any number of Decorators.
  4.  It is a wise choice to apply the Decorator Design Pattern for already shipped code. Because it enables backward compatibility of application & less unit level testing as changes do not affect other parts of code.

Summary by FAQs

When to use the Decorator Design Pattern?

-- Employ the Decorator Design Pattern when you need to be able to assign extra behaviours to objects at runtime without breaking the code that uses these objects.
-- When the class has final keyword which means the class is not further inheritable. In such cases, the Decorator Design Pattern may come to rescue.

What are the drawbacks of using the Decorator Design Pattern?

-- Decorators can complicate the process of instantiating the component because you not only have to instantiate the component but wrap it in a number of Decorators.
-- Overuse of Decorator Design Pattern may complicate the system in terms of both i.e. Maintainance & learning curve.

Difference between Adapter & Decorator Design Pattern?

-- Adapter changes the interface of an existing object
-- Decorator enhances the interface of an existing object

Difference between Proxy & Decorator Design Pattern?

-- Proxy provides a somewhat same or easy interface
-- Decorator provides enhanced interface

Design Object (computer science)

Published at DZone with permission of Vishal Chovatiya. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Design Pattern: What You Need to Know to Improve Your Code Effectively
  • Prototype Pattern in JavaScript
  • Object Relational Behavioral Design Patterns in Java
  • Distribution Design Patterns in Java - Data Transfer Object (DTO) And Remote Facade Design Patterns

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!