Definition
A decorator pattern attaches additional responsibilities to an object dynamically (at runtime). Decorators provide a flexible alternative to subclassing for extending functionalities.
Scenario
We want to give objects different options, allowing them to change dynamically during runtime.
Setting options to either true or false in a parent class is a bad idea. If we do it way, we need to change the existing code when we want to add new options. Or if we simply subclass the possible combinations, there will be too many subclasses, making it hard to maintain. Another reason is the interface segregation principle.
Inheritance is not for code reuse, and it is not for sharing code. Use composition to share behavior.
Interface segregation principle: No code should be forced to depend on methods it does not use.
Class Diagram
A decorator pattern method (Cost
) is like a recursion. It calls downward until it reaches the base, and then it returns back up.
A beverage (Decaf
and Expresso
) is the base of a recursion. An addon (CaramelDecorator
and SoyDecorator
) is each layer added, and they are decorators. A decorator is a beverage and has a beverage, and each layer (decorator) thinks it is wrapping a beverage. When Cost
is called, the outer layer can call the following layer’s Cost
because it has a beverage.
Used Cases
- We want to change how a class behaves dynamically.
- If the decorators differ significantly, like reading a file and then styling the words to bold, ilatic, and regular, etc.
Implementation
The decorator pattern might be too much for the beverage flavor example here. Since we are just adding up prices, we can simply use the iterator pattern and iterate through the addons.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// decorator.h
#ifndef __DECORATOR_H__
#define __DECORATOR_H__
#include <memory>
class IBeverage {
protected:
float price_;
public:
virtual float Cost() = 0;
};
class IAddonDecorator : public IBeverage{
protected:
std::unique_ptr<IBeverage> beverage_;
};
class Expresso : public IBeverage {
public:
Expresso();
float Cost() override;
};
class CaramelDecorator : public IAddonDecorator {
public:
CaramelDecorator(std::unique_ptr<IBeverage>&& beverage);
float Cost() override;
};
#endif //__DECORATOR_H__
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// decorator.cpp
#include "decorator.h"
Expresso::Expresso() {
price_ = 1.0;
}
float Expresso::Cost() {
return price_;
}
CaramelDecorator::CaramelDecorator(std::unique_ptr<IBeverage>&& beverage){
beverage_ = std::move(beverage);
price_ = 0.5;
}
float CaramelDecorator::Cost() {
return beverage_->Cost() + price_;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// main.cpp
#include <iostream>
#include "decorator.h"
int main() {
{
std::unique_ptr<IBeverage> expresso = std::make_unique<Expresso>();
std::cout << expresso->Cost() << std::endl;
}
std::cout << "----" << std::endl;
{
std::unique_ptr<IBeverage> caramel_expresso =
std::make_unique<CaramelDecorator>(std::make_unique<Expresso>());
std::cout << caramel_expresso->Cost() << std::endl;
}
return 0;
}