Click here to Skip to main content
15,879,326 members
Articles / Programming Languages / C++
Article

Learn Decorator Design Pattern in Easy Steps

Rate me:
Please Sign up or sign in to vote.
4.70/5 (16 votes)
5 Aug 2010CPOL6 min read 44K   217   31   13
Decorator Design Pattern explained in easy steps.

Introduction

Kartoon Kompany approaches you for re-designing their application on the famous Kelvin and Hoppes comic strip!

A quick recap about Kelvin: he is a moody brat with an extremely wild and vivid imagination. His behavior and reactions are completely different and unpredictable, and is based on his current fantasy he has dreamed up. He thinks up imaginary worlds and situations, like a spaceman battling Zorgs, TracerBazooka – Film Noir detective, imagines he is a T-rex, so on and so forth.

This is their current design:

C++
class Kelvin
{
public:
    virtual void MisBehave()
    {
        std::cout<< "Hi, I am Kelvin the Great!\n";
        //yeah, this is just simply writing text to console. 
        //But imagine if this was some functionality. 
        //Lets assume writing text to keep things simple
    }
};

class SpacemanDecorator: public Kelvin
{
public:
    virtual void MisBehave()
    {        
        //when kelvin is spaceman he does something different
        std::cout<< "The fearless Spaceman Kpiff battles with the Zork!!!\n";
    }
};

class DinoDecorator: public Kelvin
{
public:
    virtual void MisBehave()
    {
        //when kelvin is a Dinosaur, imagine the chaos!
        std::cout<< "Kelvin the terrible T-Rex runs rampant in Jurassic park!\n";
    }
};

class TracerBazookaDecorator: public Kelvin
{
public:
    virtual void MisBehave()
    {
      //Film-Noir fantasy behavior
        std::cout<< "TracerBazooka: Here's looking at you kid.\n";
    }
};

Now, Kartoon Kompany has a problem: for the next comic strip, they want to combine a T-Rex episode and a Spaceman episode in one strip. Fans go wild with anticipation.

How do we address this? Simple, let's create a DinoSpaceman behavior class where Kelvin acts accordingly, right?

Do you see how easily this could explode into a plethora of subclasses? What about a T-Rex-Spaceman episode? What about a Tracer-TRex episode? Think sub-class nuclear explosion. And we are not even talking of adding new classes like WithSusie, SnowBallFights, SantaLetters, etc.

This is the problem Kartoon Kompany approaches you with. How do you solve this design issue?

Inheritance, though powerful, is not the panacea for Kartoon Kompany’s woes. The problem with the current design is, because all behaviors are defined in sub-classes, we need to keep adding lots of sub-classes, and that is especially difficult considering Kelvin’s unimaginable imagination.

Enter Decorator Design Pattern (on an F-16 figher jet, with guns blazing)

Okay, so inheritance is out. The problem is that we are trying to define all of Kelvin’s behavior at compile time. Wouldn’t it be great if we could specify Kelvin’s behavior at run-time? Then we don’t have to sub-class, but could use composition at run-time to define Kelvin’s reactions (author’s note: don’t worry if this doesn’t make sense now, read on).

The Decorator Design Pattern does that for us! Short and sweet, Decorator does following:

  • Adds behavior to a class dynamically, at run-time. This is in contrast to inheritance, which binds behavior statically, i.e., at compile time.
  • Is an alternative to sub-classing.

Enough theory, let’s take a plunge and see how the Decorator Design Pattern helps Kelvin.

Apply the simplest of Design Principles: encapsulate and separate what varies. Since the combination of various Kelvin imagination\behavior varies, let's take them out of the Kelvin class hierarchy. I.e., TRex, Spaceman, TracerBazooka should no longer be derived classes of the Kelvin superclass. Since the function MisBehave() is common, let's create a common base class, which will be our brand new decorator base class!

C++
//derive from Kelvin so that interface of decorator is same.
//This makes life easy on the client (caller) side
//this will be our base decorator class. We are adding 
//new behavior to Kelvin thru composition
class KelvinDecorator: public Kelvin
{
public:

    //ctor taking in component to decorate (i.e. Kelvin) as argument. 
    //We need a Kelvin in order to decorate\add new behavior, 
    //there is no KelvinDecorator without Kelvin!
    KelvinDecorator(Kelvin *kelvinArgument): KelvinComponent(kelvinArgument)
    { }

    //overload common interface from Kelvin
    virtual void MisBehave()
    {
        //just ask kelvin to misbehave! The wonder of virtual keyword 
        //and polymorpishm will call correct derived class, 
        //either Decorator or main component    
        kelvinComponent->MisBehave();
    }

private:

    //Kelvin component, which we need to decorate
    Kelvin *kelvinComponent;
};

Now, with this base decorator as the super\base class, we create the Kelvin Decorator specific classes:

C++
// Just derive from our Decorator base-class and overload Kelvin’s interface
class SpacemanDecorator: public KelvinDecorator
{
public:

    SpacemanDecorator (Kelvin *kelvin): KelvinDecorator(kelvin)
    { }

    virtual void MisBehave()
    {
        // call previous decorator
        KelvinDecorator::MisBehave();
        
        //now add this decorator's additional functionality
        std::cout<< "The fearless Spaceman Kpiff battles with the Zork!!!\n";
    }
};

class DinoDecorator: public KelvinDecorator
{
public:
    DinoDecorator (Kelvin *kelvin): KelvinDecorator(kelvin)
    { }

    virtual void MisBehave()
    {
        // call previous decorator
        KelvinDecorator::MisBehave();
        
        //now add this decorator's additional functionality
        std::cout<< "Kelvin the terrible T-Rex runs rampant in Jurassic park!\n";
    }
};

class TracerBazookaDecorator: public KelvinDecorator
{
public:
    TracerBazookaDecorator (Kelvin *kelvin): KelvinDecorator(kelvin)
    { }

    virtual void MisBehave()
    {
        // call previous decorator
        KelvinDecorator::MisBehave();
        
        //now add this decorator's additional functionality
        std::cout<< "TracerBazooka: Here's looking at you kid.\n";
    }
};

That’s it! You’ve implemented the Decorator Design Pattern! Now, we let the beauty of basic OOPS do the work!

C++
Kelvin *mainComponent = new Kelvin();

KelvinDecorator *pAddedFunctionality = new SpacemanDecorator(
      new DinoDecorator (
        new TracerBazookaDecorator(mainComponent) ) );

pAddedFunctionality->MisBehave();

We’ve done nothing but add one layer of Kelvin on top of another. KelvinDecorator does its job and hands the baton to another KelvinDecorator, which in turn hands it to another, and so on and so forth. And, we did all this without modifying the main component class Kelvin!

Image 1

Congratulations, you’ve just learnt another design principle: Open-Close principle. Which simply means a class should be closed for modification, but open for extension or change.

For our example, the class Kelvin is closed for modification – we never had to modify the Kelvin class. But we still were able to alter Kelvin’s behavior through decorators (thus we made it open for extension).

And we did this by adding new layers of KelvinDecorator at run-time!

And last but not least, the output of our program:

Image 2

Things work great. Now it is easy to add and combine any of Kelvin’s behaviors with very little design change, and more importantly, all this with no change in the Kelvin class!

Change in Requirement

Let's see how easy Decorator makes it for handling change\adding new behavior.

Kartoon Kompany wants a new behavior for Kelvin for next week’s comic strip: an episode with Susie.

Our design now makes it very easy to add new behavior. If you need a new WithSusieDecorator, just add the new subclass decorator (write a WithSusieDecorator and derive it from KelvinDecorator) to get the desired functionality. In fact, do try doing this as an assignment to see how easy it is to add a new decorator with very little change to the existing design and code. Try outputting the text “GROSS! Let's hit Susie D with 1,000 snowballs!!”

You also need to create a new WithSusieDecorator() when creating layers of Kelvin Decorators in our client function (you could use a Factory for this, but that is a different design pattern for a different day!).

That’s it! How easily we have added new behavior at runtime. Can you imagine the pain and amount of changes to make if we still had used the static-subclassing way?

Kartoon Kompany is screaming with pleasure at how easy and resilient our new design is to change!

They’re so happy that they plan to introduce you in next week’s strip and let Kelvin hit you with 1,000 snowballs!

A change like that is now easy and systematic, thanks to the Decorator Design Pattern, but whether you want to put up with Kelvin’s antics is up to you!

Decorator Design Pattern Considerations

Okay, now that you’ve understood the Decorator Design Pattern, there are a few more things you should be aware of:

  1. Don’t derive KelvinDecorator directly from Kelvin. You should take out the common, required interfaces and put them as a base class (Character?), and have Kelvin derive from the Character superclass. KelvinDecorator also derives from the Character superclass. This makes the decorator light-weight, and doesn’t hog it with all unnecessary functionality from Kelvin’s class.
  2. While decorators are great, they have their disadvantages:
    • It's very difficult for a developer who doesn’t know this pattern to understand a code written with decorators. You are better than that poor dude as you now know about decorators!
    • Decorators tend to add a lot of small classes, but that’s something you have to live with.
    • Any client code that relies on RTTI (i.e., if the client checks for a derived class object type, or in simpler words, whether this is a Kelvin object) won't work for decorators. This is because you do not know which decorator layer object we have with us. It could be Spaceman, TRex, Tracer.
  3. Go read the Decorator Design Pattern from GoF book now! This article was intended to make things easy to understand, and to whet your appetite for the GoF chapter on decorators. Search for "Design Patterns: Elements of Reusable Object-Oriented Software", or the GoF book as it is affectionately called.

License

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


Written By
India India
10 years of extensive experience in full life-cycle of S\W development inlcuding Requirements, Design, Prototyping, Implementation, Verification, Release, Sustenance.

Comments and Discussions

 
QuestionImprovement over Inheritance Pin
code_ank27-Oct-14 9:15
code_ank27-Oct-14 9:15 
GeneralMy vote of 5 Pin
DNamto24-Jul-13 18:36
DNamto24-Jul-13 18:36 
GeneralRe: My vote of 5 Pin
Ananth.tm25-Jul-13 15:24
Ananth.tm25-Jul-13 15:24 
GeneralMy vote of 5 Pin
Abhinav S9-Sep-10 5:18
Abhinav S9-Sep-10 5:18 
GeneralRe: My vote of 5 Pin
Ananth.tm25-Jul-13 15:25
Ananth.tm25-Jul-13 15:25 
Generalvirtual Destructors Pin
Paul Heil10-Aug-10 5:38
Paul Heil10-Aug-10 5:38 
GeneralRe: virtual Destructors Pin
Ananth.tm10-Aug-10 15:27
Ananth.tm10-Aug-10 15:27 
QuestionCalvin and Hobbes? Pin
bling6-Aug-10 7:24
bling6-Aug-10 7:24 
AnswerRe: Calvin and Hobbes? Pin
Ananth.tm6-Aug-10 16:42
Ananth.tm6-Aug-10 16:42 
GeneralMy vote of 5 Pin
JWhattam5-Aug-10 12:36
JWhattam5-Aug-10 12:36 
Nicely written with clear explanations - maybe you would like to write on some of the other design patterns. I'm sure everyone would find it useful due to your clear writing style.
GeneralRe: My vote of 5 Pin
Ananth.tm5-Aug-10 16:52
Ananth.tm5-Aug-10 16:52 
GeneralA lot better than the article on DCL... Pin
Aescleal5-Aug-10 2:49
Aescleal5-Aug-10 2:49 
GeneralRe: A lot better than the article on DCL... Pin
Ananth.tm6-Aug-10 15:49
Ananth.tm6-Aug-10 15:49 

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.