Decorator Design Pattern in C++



A decorator is a Structural Design Pattern that allows us to add new functionalities to an existing object without changing or disturbing its structure. It lets us attach new behaviors to an object by placing that object inside another special wrapper object that contains the new behavior.

Let's understand in detail with an example. Imagine you have a simple text editor application with basic text features like writing and saving or editing text. Now, your manager wants you to add some new features like spell checking and grammar checking. If you are using the Decorator Design Pattern, you can easily add these new features without changing the existing code of the text editor. You can create separate decorator classes for spell checking and grammar checking that wrap around the original text editor class.

Decorator Design Pattern Example

Components of Decorator Design Pattern

There are four main components of Decorator Design Pattern, as shown in the following image. Also, the client is the one who uses these components to perform operations. We have added two

Components of Decorator Pattern

Let's understand each of these components in detail −

  • Component − This is an interface or abstract class that defines the common methods for both the concrete component and the decorators. In our example, this would be the interface for the text editor.
  • Concrete Component − This is the class that implements the component interface and represents the original object that we want to add new functionalities to. In our example, this would be the basic text editor class.
  • Decorator − This is an abstract class that also implements the component interface and has a reference to a component object. The decorator class is responsible for adding new functionalities to the component object. In our example, this would be the abstract decorator class for the text editor.
  • Concrete Decorators − These are the classes that extend the decorator class and implement the new functionalities. In our example, these would be the spell checker and grammar checker classes that add their respective functionalities to the text editor.

Implementation of Decorator Design Pattern in C++

Earlier, we discussed the components of the Decorator Design Pattern. Now, let's see how we can implement it in C++.

In this implementation, we will create a simple Music Player application where we have a basic music player that can play music. We will then create decorators to add new functionalities like Shuffle and Repeat to the music player.

Music Player Example Decorator Design Pattern

Steps to Implement Decorator Design Pattern

Let's see how we can implement the Decorator Design Pattern in C++

  • Create a Component interface for the music player.
  • Implement a Concrete Component class for the basic music player.
  • Create an abstract Decorator class that implements the Component interface.
  • Implement two concrete Decorator classes for shuffle and repeat functionalities.
  • Create a Client class to use the music player with decorators.

C++ Code for Decorator Design Pattern

Let's look at the C++ code for the Decorator Design Pattern implementation of a Music Player application.

Here we will use all the components of the Decorator Design Pattern that we discussed earlier. We will create a Component interface for the music player, a Concrete Component class for the basic music player, an abstract Decorator class that implements the Component interface, and concrete Decorator classes for shuffle and repeat functionalities.

#include <iostream>
using namespace std;
// Component
class MusicPlayer {
public:
   virtual void play() = 0;
   virtual ~MusicPlayer() {}
};

// Concrete Component
class BasicMusicPlayer : public MusicPlayer {
public:
   void play() {
      cout << "Playing music..." << endl;
   }
};

// Decorator
class MusicPlayerDecorator : public MusicPlayer {
protected:
   MusicPlayer* player;
public:
   MusicPlayerDecorator(MusicPlayer* p) : player(p) {}
   virtual void play() {
      player->play();
   }
   virtual ~MusicPlayerDecorator() {
      delete player;
   }
};

// Concrete Decorator for Shuffle
class ShuffleDecorator : public MusicPlayerDecorator {
public:
   ShuffleDecorator(MusicPlayer* p) : MusicPlayerDecorator(p) {}
   void play() {
      cout << "Shuffling playlist..." << endl;
      player->play();
   }
};

// Concrete Decorator for Repeat
class RepeatDecorator : public MusicPlayerDecorator {
public:
   RepeatDecorator(MusicPlayer* p) : MusicPlayerDecorator(p) {}
   void play() {
      cout << "Repeating current song..." << endl;
      player->play();
   }
};

// Client
int main() {
   MusicPlayer* player = new BasicMusicPlayer();
   MusicPlayer* shufflePlayer = new ShuffleDecorator(player);
   MusicPlayer* repeatShufflePlayer = new RepeatDecorator(shufflePlayer);

   cout << "Basic Music Player:" << endl;
   player->play();

   cout << "\nMusic Player with Shuffle:" << endl;
   shufflePlayer->play();

   cout << "\nMusic Player with Shuffle and Repeat:" << endl;
   repeatShufflePlayer->play();

   delete repeatShufflePlayer; // This will also delete shufflePlayer and player
   return 0;
}

Following is the output of the above code −

Basic Music Player:
Playing music...

Music Player with Shuffle:
Shuffling playlist...
Playing music...

Music Player with Shuffle and Repeat:
Repeating current song...
Shuffling playlist...
Playing music...

Pros and Cons of Decorator Design Pattern

The following table highlights the pros and cons of using the Decorator Design Pattern

Pros Cons
Allows adding new functionality without altering existing code. Can lead to a large number of small classes.
Promotes code reusability by using composition over inheritance. Can make the code more complex and harder to understand.
Stick to the Single Responsibility Principle by dividing functionalities into separate classes. Debugging can be more difficult due to the multiple layers of wrapping.
Enhances flexibility by allowing dynamic addition of responsibilities. There could be performance overhead due to multiple layers of wrapping.
Can be combined with other design patterns for more complex scenarios. May lead to issues with object identity and equality checks.

When to Use Decorator Design Pattern?

Following are some scenarios where you should consider using the Decorator Design Pattern

  • To add more functionalities to an object dynamically.
  • When you want to avoid class explosion due to multiple combinations of features.
  • To stick to the Single Responsibility Principle by dividing functionalities into separate classes.
  • For adding responsibilities to individual objects without affecting other objects of the same class.
  • To enhance the behavior of an object at runtime.

Real-world Applications of Decorator Design Pattern

The following image illustration some of real-world applications of Decorator Design Pattern -

Real World Applications Decorator Design Pattern

Conclusion

In this chapter, we learned about the Decorator Design Pattern, its components, implementation steps, and a C++ code example. We also discussed the pros and cons of using this pattern, when to use it, and some real-world applications. The Decorator Design Pattern is a powerful tool that allows us to add new functionalities to existing objects without changing their structure, which in turn makes it easy for us to maintain and extend our program codes.

Advertisements