Decorator Pattern

Introduction:

Decorator is the structural design pattern. It adds the new behavior to the object dynamically at the runtime without changing the other objects of the same class. Decorator classes are used to extend the functionalities of the base class by wrapper object.

This is achieved by placing the object inside the special wrapper objects that contains the behavior. To implement the wrapper , this approach uses abstract class or interfaces with composition. 

Note: Inheritance is also extend the functionalities of the base class, but that's is static behavior. Existing object functionalities cannot be changed during the runtime. You can only replace the whole object with another one that's created from a different class. Subclasses can have just one parent class. In most languages, inheritance doesn’t let a class inherit behaviors of multiple classes at the same time. It adds complexity when more number of features to get added. 

When to use?

(modify or extend the existing functionalities of the object at the runtime)

  • To add additional responsibilities to the object without changing the interfaces.
  • To extend the functionalities of the object dynamically(run time)
  • To change the functionalities of the object dynamically based on the requirements without affecting the other objects of the same class.
  • When the class declared as final that means it can't be extended further, decorator can be used to extend the functionalities.
  • Instead of editing the existing code, decorator pattern allows behavior modification at the runtime.


UML Class Diagram : 

Source Wiki












How to implement the decorator pattern ?

  1. Interface component class
  2. Concrete component class
  3. Base decorator abstract class
  4. Concrete Decorator class

Example, 

Car company is selling three models of the cars Sedan, XUV, Mini for high, medium, and low budgets respectively.  Also offering the additional features as airbag functionality for front and back passengers, rear camera, entertainment unit. 

Clients are expected to choose the car model, and additional features based on their requirements. 

You have to implement the program to predict the final price of the car based on the car model and additional features selected by the client.

UML Class diagram:


Implementation:

Sourcecode: https://github.com/krishnaKSA/design_patterns_cplusplus/blob/main/DecoratorPattern/DecoratorPattern.hpp

1.Interface class:

Here, car is the end product. So class Car created as interface which has two pure virtual functions. The class implements it has to provide the definitions of these functions. 

//Interface class
class  ICar
{
    public:
    virtual void printCarModel() = 0;
    virtual uint32_t getCost() = 0;
};

2. Concrete component class:

    As car company is selling the three different car models, created concrete component class for each car type which is inherited from the Car interface class . These concrete classes has the cost of the respective car model. 

/************* CONCRETE COMPONENT CLASS ********************/
//Sedan
class CSedan : public ICar
{
    private:
    uint32_t cost;
    public:
    //constructor
    CSedan()
    {
        //cost of this car model
        cost = 1000000;
    }
    //Implementation of the pure virtual methods declared in the Interface class
    void printCarModel() override
    {
        cout<<" : car model is Sedan "<<endl;
    }
    uint32_t getCost() override
    {
        cout << "car cost "<<cost << " "<<endl;
        return cost;
    }
};

//mini
class CMini : public ICar
{
    private:
    uint32_t cost;
    public:
    //constructor
    CMini()
    {
        //cost of this car model
        cost = 650000;
    }
    //Implementation of the pure virtual methods declared in the Interface class
    void printCarModel() override
    {
        cout<<" : car model is Mini "<<endl;
    }
    uint32_t getCost() override
    {
        cout << "car cost "<<cost << " "<<endl;
        return cost;
    }
};

//XUV
class CXUV : public ICar
{
    private:
    uint32_t cost;
    public:
    //constructor
    CXUV()
    {
        //cost of this car model
        cost = 1500000;
    }
    //Implementation of the pure virtual methods declared in the Interface class
    void printCarModel() override
    {
        cout<<" : car model is XUV "<<endl;
    }
    uint32_t getCost() override
    {
        cout << "car cost "<<cost << " "<<endl;
        return cost;
    }
};

3.Base decorator abstract class:

    Since car company is offering additional features to choose, created base decorator abstract class which is derived from ICar interface class.

This class contains the object of the interface class, and the implementations of the pure virtual functions defined in the interface class.  Interface class object is assigned in the constructor call. 

Additionally this class provides either pure virtual function or abstract function for the additional features or both. 

//Decorator class
class CCarDecorator:public ICar
{
    private:
    //interface class object
    ICar* car;

    public:
    //constructor
    CCarDecorator(ICar* carObject)
    {
        car = carObject;
    }
    //Implementation of the pure virtual methods declared in the Interface class
    void printCarModel() override
    {
        car->printCarModel();
    }
    uint32_t getCost() override
    {
        return car->getCost();
    }
    //additional features
    virtual void additionalFeatures() = 0;
};

4.Concrete decorator class:

As car company is offering three additional features, added three concrete decorator classes which are derived from base decorator class. 

In the constructor call, this class calls the constructor of the base decorator class and pass the instance of the ICar class. 

These classes either provides the implementation of the pure virtual methods declared in the interface class or uses the implementation available from the base decorator class. Also these classes contains the functions for additional features to add on top of the concrete component class. 

//AirBag feature
class CAirBags:public CCarDecorator
{
    public:
    //Enum for air bag types
    enum eAirBagType
    {
        eFront = 0,
        eBack
    };

    //constructor
    CAirBags(ICar* carObject,eAirBagType type):CCarDecorator(carObject)
    {      
        //car object and type of the air bag selected by the customer passed as argument
        //of the constrcutor call  
        airBagSupport = type;

        //cost calculations
        if(eFront == airBagSupport)
        {
            costOfAirBags = 20000;
        }
        else if(eBack == airBagSupport)
        {
            costOfAirBags = 40000;
        }
        else{
            airBagSupport = eFront;
            costOfAirBags = 20000;
        }

    }
    //additional features
    uint32_t getCost() override
    {
        cout <<"Airbag cost = "<< costOfAirBags <<" ";
        return CCarDecorator::getCost() + costOfAirBags;
    }    

    void printCarModel() override
    {
        additionalFeatures();
        CCarDecorator::printCarModel();
    }

    void additionalFeatures() override
    {
        cout<<"airbags added for "<<((eFront == airBagSupport) ? " front passengers " : "front and back passengers ");
    }
   
    private:
    //air bag cost
    uint32_t costOfAirBags;
    //type of the air bag selected by the user
    eAirBagType airBagSupport;
};

//Rear Camera
class CRearCamera:public CCarDecorator
{
    private:
    uint32_t costOfRearCamera;

    public:
    //car object and type of the air bag selected by the customer passed as argument
    //of the constrcutor call  
    CRearCamera(ICar* carObject):CCarDecorator(carObject)
    {
        costOfRearCamera = 250000;
    }
    //Additional features
    uint32_t getCost() override
    {
        cout <<"cost of rearcamera = "<< costOfRearCamera <<" ";
        return CCarDecorator::getCost() + costOfRearCamera;
    }

    void additionalFeatures() override
    {
        cout<<"rear camera added ";
    }

    void printCarModel() override
    {
        additionalFeatures();
        CCarDecorator::printCarModel();
    }
};

class CEntertainmentUnit:public CCarDecorator
{
    public:
    //enum for HU models
    enum eHUModel
    {
        eBasic = 0,
        eMedium = 1,
        eAdvanced
    };

    //car object and type of the air bag selected by the customer passed as argument
    //of the constrcutor call  
    CEntertainmentUnit(ICar* carObject,eHUModel type):CCarDecorator(carObject)
    {        
        HUModel = type;
        if(eBasic == HUModel)
        {
            costOfHU = 20000;
        }
        else if(eMedium == HUModel)
        {
            costOfHU = 32000;
        }
        else if(eAdvanced == HUModel)
        {
            costOfHU = 45000;
        }

    }

    //Additional features
    uint32_t getCost() override
    {
        cout << "HU cost " << costOfHU <<" ";
        return CCarDecorator::getCost() + costOfHU;
    }

    void additionalFeatures() override
    {
        cout<<"Entertainment unit added with Model "<<((HUModel == eBasic )?" Basic ":((HUModel == eMedium ) ? "Medium " : "Advanced "))<<" , ";
    }

    void printCarModel() override
    {
        additionalFeatures();
        CCarDecorator::printCarModel();
    }

    private:
    uint32_t costOfHU; //cost of the HU
    eHUModel HUModel; //HU model types
};


Client code:

void testDecoratorpattern()
{
    //Chosing sedan with front and back airbags and Medium type HU model
    ICar* carModel = new CSedan;
    carModel = new CAirBags(carModel, CAirBags::eBack);
    carModel = new CEntertainmentUnit(carModel, CEntertainmentUnit::eMedium);    
    carModel->printCarModel();
    cout <<endl << "cost = "<<carModel->getCost();

    //Chosing Mini with front airbags and basic type HU model
    carModel = new CMini;
    carModel = new CAirBags(carModel, CAirBags::eFront);
    carModel = new CEntertainmentUnit(carModel, CEntertainmentUnit::eBasic);
    carModel->printCarModel();
    cout <<endl << "cost = "<<carModel->getCost();

    //Chosing XUV with rear camera, front and back airbags and advanced type HU model
    carModel = new CXUV;
    carModel = new CRearCamera(carModel);
    carModel = new CEntertainmentUnit(carModel, CEntertainmentUnit::eAdvanced);
    carModel = new CAirBags(carModel, CAirBags::eBack);
    carModel->printCarModel();
    cout <<endl <<"cost = "<<carModel->getCost();
}

Program output: