## Decorator The last design pattern that I mention today is a `decorator`. Before we talk about it, let's extend `Cargo` class and handle a few new functionalities: * The item may be damaged during a time (we have salty air) so it can lose his value during travelling * The fruit may rotten, so we also want to lower its price every day * The alcohol price should be based on it's power * The value of item should be higher based on its type (common is cheapest but legendary has a big value) ___ ## New implementation Let's start with a new implementation of class `Fruit`. Now we inherit from `TimeObserver` to count days. We also take in account that the price will be lowered every day. ```C++ struct Fruit : public Cargo, public TimeObserver { static constexpr int MAX_TIME = 20; static constexpr int BASE_PRICE = 10; int rottenCounter; Time* time; Fruit(Time* time, size_t amount, int rottenCounter) : Cargo(amount), time(time), rottenCounter(rottenCounter) { time->attach(this); } // Rule of 5 ~Fruit() { time->detach(this); } size_t getPrice() const override { return (rottenCounter * BASE_PRICE) / MAX_TIME; } const std::string& name() const override { static const std::string name = "Banana"; return name; } void nextDay() override { rottenCounter = std::max(0, rottenCounter - 1); } }; ``` ___ ```C++ struct Item : public Cargo, public TimeObserver { enum class Type { Common = 100, Rare = 300, Epic = 1000, Legendary = 2500 }; static constexpr int MAX_DURABILITY = 100; Time* time; Type type; int durability{MAX_DURABILITY}; Item(Time* time, size_t amount, Type type) : Cargo(amount), time(time), type(type) { time->attach(this); } // Rule of 5 ~Item() { time->detach(this); } size_t getPrice() const override { return (durability * static_cast(type)) / MAX_DURABILITY; } const std::string& name() const override { static const std::string name = "Item"; return name; } void nextDay() override { durability = std::max(0, durability - 1); } }; ``` ___ ## Duplication of code At the end, we also implement a `Alcohol` class. As you can see, we duplicate behavior few times. `Item` and `Fruit` may lose value each day. Price of `Item` and `Alcohol` depend on it's type etc… ```C++ struct Alcohol : public Cargo { enum class Type { White = 100, Spiced = 200, Dark = 300, Seasoned = 500 }; static constexpr int MAX_POWER = 96; int power; Type type; Alcohol(size_t amount, int power, Type type) : Cargo(amount), power(power), type(type) {} size_t getPrice() const override { return (power * static_cast(type)) / MAX_POWER; } const std::string& name() const override { static const std::string name = "Rum"; return name; } }; ``` ___ The following code works as we want to. However, whenever we want to change a logic, for instance instead of decrement the counter every day, we want to subtract `durability` and `rottenCounter` every second day. We need to do this in two places. And when we will add more cargo, we will need to do this everywhere. This is not a good example of easy to maintenance code. ```C++ int main() { Time time; std::unique_ptr cargo = std::make_unique(&time, 10, Item::Type::Epic); for (int i = 1; i <= 15; ++i) { std::cout << "DAY: " << i << " | Name: " << cargo->name() << " | Price: " << cargo->getPrice() << '\n'; ++time; } } ``` ``` DAY: 1 | Name: Item | Price: 1000 DAY: 2 | Name: Item | Price: 990 DAY: 3 | Name: Item | Price: 980 DAY: 4 | Name: Item | Price: 970 DAY: 5 | Name: Item | Price: 960 DAY: 6 | Name: Item | Price: 950 DAY: 7 | Name: Item | Price: 940 DAY: 8 | Name: Item | Price: 930 DAY: 9 | Name: Item | Price: 920 DAY: 10 | Name: Item | Price: 910 DAY: 11 | Name: Item | Price: 900 DAY: 12 | Name: Item | Price: 890 DAY: 13 | Name: Item | Price: 880 DAY: 14 | Name: Item | Price: 870 DAY: 15 | Name: Item | Price: 860 ``` ___ ## Drawback According to one of SOLID rule **O**pen-close, we should write an easy to extend code, which don't require from us modification of already implemented code. Unfortunately, we need to modify each class to append a new logic, like: `decrement durability each day` or `get price based on durability or type`. What's more, we don't want to simulate `rotten of fruit` or lose `durability` by items that are stored in `shops`. So we need to add some `bool` flag like `isInShop` to ignore time elapsing. When the code grows and grows, we need to add more such flags and duplicate more code. This is definitely not the way we should develop a code. A better solution is to make a `Fruit` or `Item` class unaware of time elapsing. We can also make class `item` and `Alcohol` unaware of different types, and it's prices. This will allow to decouple the code, and make it easier to extend. ___ ## Decorator Instead of implement the whole logic to every class, and then make some `bool` flags to omit a few of them. We can use a `decorator` pattern. Let's start from the interface. The `DecoratedCargo` inherit form `Cargo` class and take as an argument `unique_ptr` for class `Cargo`. This interface has also two protected function allowing to get underlying cargo. ```C++ class DecoratedCargo : public Cargo { public: explicit DecoratedCargo(std::unique_ptr&& cargo) : _cargo(std::move(cargo)) { assert(_cargo); } protected: Cargo& cargo() { return *_cargo; } const Cargo& cargo() const { return *_cargo; } private: std::unique_ptr _cargo; }; ``` ___ ## Vulnerable A `Vulnerable` class inherit from `TimeObserver` to simulate time elapsing and from `DecoratedCargo`. When we calculate the price, it will calculate percentage value based on `durability` and `maxDurability`. Price can be manipulated by other decorator, so we call `getPrice`. ```C++ class Vulnerable : public DecoratedCargo, public TimeObserver { public: Vulnerable(std::unique_ptr&& cargo, Time* time, int durability, int maxDurability) : DecoratedCargo(std::move(cargo)), _time(time), _durability(durability), _maxDurability(maxDurability) { assert(_time); time->attach(this); } // Rule of 5 ~Vulnerable() { _time->detach(this); } void nextDay() override { _durability = std::max(0, _durability - 1); } size_t getPrice() const override { return (_durability * cargo().getPrice()) / _maxDurability; } const std::string& name() const override { return cargo().name(); } private: Time* _time; int _durability; int _maxDurability; }; ``` ___ ## Valuable A `Valuable` class is a template class that take `enum` as a template argument. It will be used later when we calculate a price. We will treat it as a multiplier for already calculated price. ```C++ template class Valuable : public DecoratedCargo { public: Valuable(std::unique_ptr&& cargo, ValueType value) : DecoratedCargo(std::move(cargo)), _value(value) { } size_t getPrice() const override { return cargo().getPrice() * static_cast(_value); } const std::string& name() const override { return cargo().name(); } private: ValueType _value; }; ``` ___ ## Type Because we want to make `Item` and `Alcohol` and any other type unaware about its type. We move it to the separate files. This allows us to develop new functionality without modification of existing code. For instance, when we decided later that we want to add also a `type` for `Fruit` we will create a new file and that's all! We don't need to recompile anything, and also don't need to modify anything. So even if `Fruit` is in a separate repository, and we can't modify it. We still can add new behavior. ```C++ enum class ItemType { Common = 1, Rare = 3, Epic = 10, Legendary = 25 }; enum class AlcoholType { White = 1, Spiced = 2, Dark = 3, Seasoned = 5 }; ``` ___ ## Usage Now we can concatenate any decorator with a `Cargo` type, so we can add new functionality `in fly`. ```C++ int main() { Time time; std::unique_ptr cargo = std::make_unique( std::make_unique>( std::make_unique(10), ItemType::Epic), &time, 100, 100); for (int i = 1; i <= 15; ++i) { std::cout << "DAY: " << i << " | Name: " << cargo->name() << " | Price: " << cargo->getPrice() << '\n'; ++time; } } ``` ```bash DAY: 1 | Name: Item | Price: 1000 DAY: 2 | Name: Item | Price: 990 DAY: 3 | Name: Item | Price: 980 DAY: 4 | Name: Item | Price: 970 DAY: 5 | Name: Item | Price: 960 DAY: 6 | Name: Item | Price: 950 DAY: 7 | Name: Item | Price: 940 DAY: 8 | Name: Item | Price: 930 DAY: 9 | Name: Item | Price: 920 DAY: 10 | Name: Item | Price: 910 DAY: 11 | Name: Item | Price: 900 DAY: 12 | Name: Item | Price: 890 DAY: 13 | Name: Item | Price: 880 DAY: 14 | Name: Item | Price: 870 DAY: 15 | Name: Item | Price: 860 ``` ___ ## Store If cargo is inside the store, we just don't add a `Vulnerable` decorator! ```C++ Time time; std::unique_ptr cargo = std::make_unique( std::make_unique>( std::make_unique(10), ItemType::Epic), &time, 100, 100); std::unique_ptr cargo2 = std::make_unique>( std::make_unique(10), ItemType::Epic); for (int i = 1; i <= 15; ++i) { std::cout << "DAY: " << i << " | Name: " << cargo->name() << " | Price: " << cargo->getPrice(); std::cout << " |-| Name: " << cargo2->name() << " | Price: " << cargo2->getPrice() << '\n'; ++time; } ``` ```bash DAY: 1 | Name: Item | Price: 1000 |-| Name: Item | Price: 1000 DAY: 2 | Name: Item | Price: 990 |-| Name: Item | Price: 1000 DAY: 3 | Name: Item | Price: 980 |-| Name: Item | Price: 1000 DAY: 4 | Name: Item | Price: 970 |-| Name: Item | Price: 1000 DAY: 5 | Name: Item | Price: 960 |-| Name: Item | Price: 1000 DAY: 6 | Name: Item | Price: 950 |-| Name: Item | Price: 1000 DAY: 7 | Name: Item | Price: 940 |-| Name: Item | Price: 1000 DAY: 8 | Name: Item | Price: 930 |-| Name: Item | Price: 1000 DAY: 9 | Name: Item | Price: 920 |-| Name: Item | Price: 1000 DAY: 10 | Name: Item | Price: 910 |-| Name: Item | Price: 1000 DAY: 11 | Name: Item | Price: 900 |-| Name: Item | Price: 1000 DAY: 12 | Name: Item | Price: 890 |-| Name: Item | Price: 1000 DAY: 13 | Name: Item | Price: 880 |-| Name: Item | Price: 1000 DAY: 14 | Name: Item | Price: 870 |-| Name: Item | Price: 1000 DAY: 15 | Name: Item | Price: 860 |-| Name: Item | Price: 1000 ``` ___ ## Decorator The decorator pattern can be used to extend (decorate) the functionality of a certain object statically, or in some cases at run-time, independently of other instances of the same class, provided some groundwork is done at design time. This is achieved by designing a new Decorator class that wraps the original class. Decorator UML ___ Decorator also helps with **D**ependency inversion. Because `decorator` classes are in low-lvl, so we can easily add a new decorator without changing anything in higher layers. Decorator Cargo UML ___ ## Drawbacks Decorator is powerfull desing pattenr, but as everything it also has some drawbacks. * The main problem is that we need to use plenty of `unique_ptr` this will use more memory and also will be slower, because we need to call a few virtual functions. * If we add to many decorators, the code became hard to read * Debugging code containing decorators also may be more difficult, because we need to jump between a few of the same methods (but located in different decorators) before we reach the final result. So find a bug will be harder * If the result of one decorator has an impact on another, we may cause a hard to spot bug when we swap the order. So we shouldn't create a decorator, which should be stored in order, because a risk of concatenated them wrong is high. ___ ## Different approach As one popular proverb says: "Time is money". And if our code must be as fast as possible, and we can't afford a price, that few decorators means calling of few virtual functions, which also make the code much harder to optimize by compiler. We need to rising with decorator. I have good news for you: No, you don't need to! We have two types of polymorphism, so instead of using a `dynamic` one, let's use a `static`. ___ ## Get rid of virtual First, the class `Cargo` is no longer virtual. We don't need to implement rule of 5 and create a virtual destructor. We move constructors to `protected` section, to make it create only by derived classes (sth similar to abstract class, which we can't create). ```C++ struct Cargo { size_t amount; auto operator<=>(const Cargo&) const = default; protected: // Only derived class can create it Cargo(size_t amount) : amount(amount) {} Cargo() = default; }; ``` ___ Next, we also remove all virtual functions from derived classes. ```C++ struct Fruit : public Cargo { static constexpr int BASE_PRICE = 10; Fruit(size_t amount) : Cargo(amount) {} size_t getPrice() const { return BASE_PRICE; } const std::string& name() const { static const std::string name = "Banana"; return name; } }; ``` ```C++ struct Item : public Cargo { static constexpr int BASE_PRICE = 100; Item(size_t amount) : Cargo(amount) {} size_t getPrice() const { return BASE_PRICE; } const std::string& name() const { static const std::string name = "Item"; return name; } }; ``` ___ We don't need to have a base class for decorator. We can move directly to implementation. ```C++ template class Vulnerable : public TimeObserver { public: Vulnerable(Neasted neasted, Time* time, int durability, int maxDurability) : _neasted(neasted), _time(time), _durability(durability), _maxDurability(maxDurability) { assert(_time); time->attach(this); } // Rule of 5 ~Vulnerable() { _time->detach(this);} void nextDay() override { _durability = std::max(0, _durability - 1); } size_t getPrice() const { return (_durability * _neasted.getPrice()) / _maxDurability; } const std::string& name() const { return _neasted.name(); } private: Neasted _neasted; Time* _time; int _durability; int _maxDurability; }; ``` ___ As you can see, we have even less code, and we avoid a lot of virtual functions. If we decide that an object is too heavy to copy, we can pass a pointer. The main bottleneck of decorator which slows down the code was calling a lot of virtual functions. Without virtual functions, our code is much faster. ```C++ template class Valuable { public: Valuable(Neasted neasted, ValueType value) : _neasted(neasted), _value(value) { } size_t getPrice() const { return _neasted.getPrice() * static_cast(_value); } const std::string& name() const { return _neasted.name(); } private: Neasted _neasted; ValueType _value; }; ``` ___ Even usage seems more intuitive, because we don't need to create a lot of `unique_ptr` even for small objects. ```C++ int main() { Time time; auto cargo = Vulnerable(Valuable(Item(10), ItemType::Epic), &time, 100, 100); auto cargo2 = Valuable(Item(10), ItemType::Epic); for (int i = 1; i <= 15; ++i) { std::cout << "DAY: " << i << " | Name: " << cargo.name() << " | Price: " << cargo.getPrice(); std::cout << " |-| Name: " << cargo2.name() << " | Price: " << cargo2.getPrice() << '\n'; ++time; } } ``` ```bash DAY: 1 | Name: Item | Price: 1000 |-| Name: Item | Price: 1000 DAY: 2 | Name: Item | Price: 990 |-| Name: Item | Price: 1000 DAY: 3 | Name: Item | Price: 980 |-| Name: Item | Price: 1000 DAY: 4 | Name: Item | Price: 970 |-| Name: Item | Price: 1000 DAY: 5 | Name: Item | Price: 960 |-| Name: Item | Price: 1000 DAY: 6 | Name: Item | Price: 950 |-| Name: Item | Price: 1000 DAY: 7 | Name: Item | Price: 940 |-| Name: Item | Price: 1000 DAY: 8 | Name: Item | Price: 930 |-| Name: Item | Price: 1000 DAY: 9 | Name: Item | Price: 920 |-| Name: Item | Price: 1000 DAY: 10 | Name: Item | Price: 910 |-| Name: Item | Price: 1000 DAY: 11 | Name: Item | Price: 900 |-| Name: Item | Price: 1000 DAY: 12 | Name: Item | Price: 890 |-| Name: Item | Price: 1000 DAY: 13 | Name: Item | Price: 880 |-| Name: Item | Price: 1000 DAY: 14 | Name: Item | Price: 870 |-| Name: Item | Price: 1000 DAY: 15 | Name: Item | Price: 860 |-| Name: Item | Price: 1000 ``` ___ ## Ecerice 1 * Go into directory Ship and implement DecoratedCargo: * It should take a std::unique_ptr<Cargo> and has protected getter to this field. * Implement Vunreable class: * It should inherit from TimeObserver and DecoratedCargo * It should take additional parameters in C'tor: Time, durability and maxDurability * It should simulate time elapsing. Every day should subtract durability * It should return price back on current durability of cargo ___ ## Exercise 2 * Implement Valuable class: * It should inherit from DecoratedCargo * it should be a template that takes enum type of value * It should return value based on type (cast it to enum and multiply by current value) * Try to compile code form main.cpp * You may have some trouble with running code if you don't write a code carefully :) think where is a problem.