Decorator Pattern

The Decorator design pattern allows you to add new behaviors or functionalities to objects dynamically without modifying their original structure.

It is like adding layers of decorations to an object, enhancing its features as needed.

In simple terms, think of a cake that you can decorate with various toppings, such as fruits, chocolates, or sprinkles. The cake remains the core object, but you can add different toppings (decorators) to customize its appearance and taste.

Decorator design pattern Class Diagram:


Below is the class diagram for the Decorator design pattern:

  • Component: This is the interface or abstract class that defines the common operations that can be performed by both the concrete component and its decorators.
  • Concrete Component: This is the core class that implements the Component interface. It represents the original object to which decorators can be added.
  • Decorator: This is the abstract class that also implements the Component interface and contains a reference to a Component object. It acts as a base class for concrete decorators and allows them to extend the behavior of the wrapped Component.
  • Concrete Decorator: These are the classes that extend the Decorator class and add specific functionalities or decorations to the Component.

Decorator design pattern C++ Example:

Let’s create a simple example of the Decorator pattern to demonstrate how decorators can enhance the behavior of a core object, a Car, by adding additional features.

#include <iostream>

// Component: Interface for the Car
class Car {
public:
    virtual void drive() const = 0;
    virtual ~Car() {}
};

// Concrete Component: The basic Car
class BasicCar : public Car {
public:
    void drive() const override {
        std::cout << "Driving the basic car." << std::endl;
    }
};

// Decorator: Base class for Decorators
class CarDecorator : public Car {
public:
    CarDecorator(Car* car) : car_(car) {}

    void drive() const override {
        car_->drive();
    }

protected:
    Car* car_;
};

// Concrete Decorator: Adds AC feature
class ACDecorator : public CarDecorator {
public:
    ACDecorator(Car* car) : CarDecorator(car) {}

    void drive() const override {
        car_->drive();
        std::cout << "With AC on." << std::endl;
    }
};

// Concrete Decorator: Adds Music system feature
class MusicSystemDecorator : public CarDecorator {
public:
    MusicSystemDecorator(Car* car) : CarDecorator(car) {}

    void drive() const override {
        car_->drive();
        std::cout << "With music system playing." << std::endl;
    }
};

int main() {
    // Create a basic car
    Car* basicCar = new BasicCar();
    basicCar->drive(); // Output: Driving the basic car.

    // Decorate the basic car with AC and music system
    Car* carWithACAndMusic = new MusicSystemDecorator(new ACDecorator(basicCar));
    carWithACAndMusic->drive();
    // Output: Driving the basic car.
    //         With AC on.
    //         With music system playing.

    // Clean up
    delete basicCar;
    delete carWithACAndMusic;

    return 0;
}

Decorator design pattern Explanation:

In this example, we have a Car interface, which represents the Component. The BasicCar class is the Concrete Component, which implements the Car interface and represents the core object we want to decorate.

We then create a CarDecorator class, which acts as the Decorator. It also implements the Car interface but contains a reference to a Car object. This allows the decorators to wrap around the Car object and add functionality.

We have two Concrete Decorator classes, ACDecorator and MusicSystemDecorator, which extend the CarDecorator class. They add specific features, such as AC and a music system, to the Car.

In the main() function, we demonstrate how the Decorator pattern works. We create a basic car and then decorate it with ACDecorator and MusicSystemDecorator. As a result, we get a car with both AC and a music system.

Decorator design pattern Summary:

The Decorator pattern allows you to add features to an object dynamically without altering its structure. It follows the principle of open-closed design, as you can add new decorations without modifying the core object. By using the Decorator pattern, you can achieve flexible and reusable designs that allow for easy customization and extension of the functionality.