735 lines
25 KiB
Markdown
735 lines
25 KiB
Markdown
# Strategy
|
||
|
||
The second most popular design pattern is a strategy. If I need to bet how many of you used this pattern at least once I will say all of you. Strategy is the most common pattern used by the STL library and makes it so flexible. That's why we should use it often (maybe not everywhere) but in most cases, strategy will be an answer.
|
||
|
||
Why we should use strategy? As David Thomas and Andrew Hunt said in the book: *Inheritance is Rarely the Answer*.
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
___
|
||
|
||
## Example of bad inheritance
|
||
|
||
Before we talk about strategy patterns, let's look at the bad design of the application. We want to create three types of ammunition: *Round*, *Chain* and *Grape*. Each type of ammunition needs to be displayed as an icon (for instance when we check, the inventory, or buy it from the store) and also needs to be animated when a ship `fires` this ammunition during combat.
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
We decided to solve this problem by create inheritance. We inherit from class `Ammunition` and create three classes of `Shots`, then depending on which graphic library we used we `draw` it using `metal` or `OpenGL`.
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
<img data-src="images/ammunition_uml.png" alt="Amunnition UML">
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.86em" -->
|
||
___
|
||
|
||
## Next iteration
|
||
|
||
After we successfully implemented the `draw` method, we decided to add `SDL` and `WindowsAnimationManager` libraries for displaying animation during combat. Because we already use `OpenGL` and `metal` libraries, we still need to support them. So we need to create another layer of inheritance.
|
||
|
||
<img data-src="images/ammunition_uml2.png" alt="Amunnition UML">
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.92em" -->
|
||
|
||
___
|
||
|
||
I hope all of you saw that this is not a good example of a design application. Each time we add support for a new library, we need to create another layer of inheritance. How we can make it more flexible? The answer is the `Strategy` design pattern.
|
||
|
||
<img data-src="images/strategy.png" alt="Amunnition UML">
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.92em" -->
|
||
|
||
___
|
||
|
||
## Simplify
|
||
|
||
First of all, we don't want to break 2 of the SOLID letters: **L**iskov substitution and **S**ingle responsibility. That's why we should inherit from class which all methods make sense for us. We create two strategies, one for `fire` and one for `draw`. We also want to avoid an oversized hierarchy of class.
|
||
|
||
<img data-src="images/ammunition_uml3.png" alt="Amunnition UML">
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.92em" -->
|
||
|
||
___
|
||
|
||
## Implementation
|
||
|
||
The ammunition class will be a base class with the interface. It also contains info about the amount of ammunition.
|
||
|
||
```C++
|
||
class Ammunition {
|
||
public:
|
||
Ammunition(size_t amount)
|
||
: _amount(amount) {}
|
||
virtual ~Ammunition() = default;
|
||
|
||
virtual void fire() = 0;
|
||
virtual void draw() = 0;
|
||
virtual int getDamage() const = 0;
|
||
|
||
size_t amount() const { return _amount; }
|
||
|
||
private:
|
||
size_t _amount = 0;
|
||
};
|
||
```
|
||
___
|
||
|
||
```C++
|
||
class FireStrategy {
|
||
public:
|
||
virtual void fire(const Ammunition& ammunition) = 0;
|
||
};
|
||
|
||
class SDLFireStrategy : public FireStrategy {
|
||
public:
|
||
void fire(const Ammunition& ammunition) override {
|
||
std::cout << "SDL Deal: " << ammunition.getDamage() << " damage!" << '\n';
|
||
}
|
||
};
|
||
|
||
class WAMFireStrategy : public FireStrategy {
|
||
public:
|
||
void fire(const Ammunition& ammunition) override {
|
||
std::cout << "WAM Deal: " << ammunition.getDamage() << " damage!" << '\n';
|
||
}
|
||
};
|
||
```
|
||
|
||
```C++
|
||
class DrawStrategy {
|
||
public:
|
||
virtual void draw(const Ammunition& ammunition) = 0;
|
||
};
|
||
|
||
class OpenGLDrawStrategy : public DrawStrategy {
|
||
public:
|
||
void draw(const Ammunition& ammunition) override {
|
||
std::cout << "OpenGL Ammunition: " << ammunition.amount() << '\n';
|
||
}
|
||
};
|
||
|
||
class MetalDrawStrategy : public DrawStrategy {
|
||
public:
|
||
void draw(const Ammunition& ammunition) override {
|
||
std::cout << "Metal Ammunition: " << ammunition.amount() << '\n';
|
||
}
|
||
};
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.60em" -->
|
||
___
|
||
|
||
```C++
|
||
class RoundShot : public Ammunition {
|
||
public:
|
||
RoundShot(size_t amount, std::unique_ptr<FireStrategy>&& fireStrategy, std::unique_ptr<DrawStrategy>&& drawStrategy)
|
||
: Ammunition(amount), _fireStrategy(std::move(fireStrategy)), _drawStrategy(std::move(drawStrategy)) {}
|
||
void fire() override { _fireStrategy->fire(*this /* other args if needed*/); }
|
||
void draw() override { _drawStrategy->draw(*this /* other args if needed*/); }
|
||
int getDamage() const override { return 10; }
|
||
private:
|
||
std::unique_ptr<FireStrategy> _fireStrategy;
|
||
std::unique_ptr<DrawStrategy> _drawStrategy;
|
||
};
|
||
```
|
||
|
||
```C++
|
||
class ChainShot : public Ammunition {
|
||
public:
|
||
ChainShot(size_t amount, std::unique_ptr<FireStrategy>&& fireStrategy, std::unique_ptr<DrawStrategy>&& drawStrategy)
|
||
: Ammunition(amount), _fireStrategy(std::move(fireStrategy)), _drawStrategy(std::move(drawStrategy)) {}
|
||
void fire() override { _fireStrategy->fire(*this /* other args if needed*/); }
|
||
void draw() override { _drawStrategy->draw(*this /* other args if needed*/); }
|
||
int getDamage() const override { return 15; }
|
||
private:
|
||
std::unique_ptr<FireStrategy> _fireStrategy;
|
||
std::unique_ptr<DrawStrategy> _drawStrategy;
|
||
};
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
```C++
|
||
class GrapeShot : public Ammunition {
|
||
public:
|
||
GrapeShot(size_t amount, std::unique_ptr<FireStrategy>&& fireStrategy, std::unique_ptr<DrawStrategy>&& drawStrategy)
|
||
: Ammunition(amount), _fireStrategy(std::move(fireStrategy)), _drawStrategy(std::move(drawStrategy)) {}
|
||
void fire() override { _fireStrategy->fire(*this /* other args if needed*/); }
|
||
void draw() override { _drawStrategy->draw(*this /* other args if needed*/); }
|
||
int getDamage() const override { return 20; }
|
||
private:
|
||
std::unique_ptr<FireStrategy> _fireStrategy;
|
||
std::unique_ptr<DrawStrategy> _drawStrategy;
|
||
};
|
||
```
|
||
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.60em" -->
|
||
___
|
||
|
||
## Usage
|
||
|
||
We can easily inject any strategy into an ammunition class. This will help us keep another solid rule **D**ependency inversion and also dependency injection.
|
||
|
||
```C++
|
||
int main() {
|
||
std::unique_ptr<Ammunition> grapeShot = std::make_unique<GrapeShot>(
|
||
100, std::make_unique<SDLFireStrategy>(), std::make_unique<OpenGLDrawStrategy>());
|
||
|
||
grapeShot->fire();
|
||
grapeShot->draw();
|
||
}
|
||
```
|
||
|
||
```bash
|
||
SDL Deal: 20 damage!
|
||
OpenGL Ammunition: 100
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.92em" -->
|
||
___
|
||
|
||
## Drawback
|
||
|
||
The main drawback of the strategy pattern is visible when we try to add a new type. In such case, we need to implement a new behavior for each of these classes:
|
||
|
||
* <!-- .element: class="fragment fade-in" --> DrawOpenGLStrategy
|
||
* <!-- .element: class="fragment fade-in" --> DrawMetalStrategy
|
||
* <!-- .element: class="fragment fade-in" --> WamFireStrategy
|
||
* <!-- .element: class="fragment fade-in" --> SDLFireStrategy
|
||
|
||
This will be not such big deal, when we have access to all of the source code, but in case, when we can't modify this it will be problematic
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
___
|
||
|
||
## Improvement
|
||
|
||
For now, `DrawStrategy` and `FireStrategy`, have one `virtual` function which probably needs to distinguish which type of ammunition should be drawn or animate. That's why we can create a more flexible interface, which takes a specific type and we don't need to use any casting.
|
||
|
||
```C++
|
||
class FireStrategy {
|
||
public:
|
||
virtual void fire(const ChainShot& shot) = 0;
|
||
virtual void fire(const RoundShot& shot) = 0;
|
||
virtual void fire(const GrapeShot& shot) = 0;
|
||
};
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
___
|
||
|
||
```C++
|
||
class SDLFireStrategy : public FireStrategy {
|
||
public:
|
||
void fire(const ChainShot& shot) override {
|
||
std::cout << "SDL Chain Shot Deal: " << shot.getDamage() << " damage!" << '\n';
|
||
}
|
||
void fire(const RoundShot& shot) override {
|
||
std::cout << "SDL Round Shot Deal: " << shot.getDamage() << " damage!" << '\n';
|
||
}
|
||
void fire(const GrapeShot& shot) override {
|
||
std::cout << "SDL Grape Shot Deal: " << shot.getDamage() << " damage!" << '\n';
|
||
}
|
||
};
|
||
```
|
||
|
||
```C++
|
||
class WAMFireStrategy : public FireStrategy {
|
||
public:
|
||
void fire(const ChainShot& shot) override {
|
||
std::cout << "WAM Chain Shot Deal: " << shot.getDamage() << " damage!" << '\n';
|
||
}
|
||
void fire(const RoundShot& shot) override {
|
||
std::cout << "WAM Round Shot Deal: " << shot.getDamage() << " damage!" << '\n';
|
||
}
|
||
void fire(const GrapeShot& shot) override {
|
||
std::cout << "WAM Grape Shot Deal: " << shot.getDamage() << " damage!" << '\n';
|
||
}
|
||
};
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.62em" -->
|
||
___
|
||
|
||
```C++
|
||
class DrawStrategy {
|
||
public:
|
||
virtual void draw(const ChainShot& shot) = 0;
|
||
virtual void draw(const RoundShot& shot) = 0;
|
||
virtual void draw(const GrapeShot& shot) = 0;
|
||
};
|
||
```
|
||
|
||
```C++
|
||
class OpenGLDrawStrategy : public DrawStrategy {
|
||
public:
|
||
void draw(const ChainShot& shot) override {
|
||
std::cout << "OpenGL Chain Shot: " << shot.amount() << '\n';
|
||
}
|
||
void draw(const RoundShot& shot) override {
|
||
std::cout << "OpenGL Round Shot: " << shot.amount() << '\n';
|
||
}
|
||
void draw(const GrapeShot& shot) override {
|
||
std::cout << "OpenGL Grape Shot: " << shot.amount() << '\n';
|
||
}
|
||
};
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
```C++
|
||
class MetalDrawStrategy : public DrawStrategy {
|
||
public:
|
||
void draw(const ChainShot& shot) override {
|
||
std::cout << "Metal Chain Shot: " << shot.amount() << '\n';
|
||
}
|
||
void draw(const RoundShot& shot) override {
|
||
std::cout << "Metal Round Shot: " << shot.amount() << '\n';
|
||
}
|
||
void draw(const GrapeShot& shot) override {
|
||
std::cout << "Metal Grape Shot: " << shot.amount() << '\n';
|
||
}
|
||
};
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.62em" -->
|
||
___
|
||
|
||
## Usage
|
||
|
||
Now the function exactly knows, which type is handling, and can animate it or draw it correctly, without any type checking.
|
||
|
||
```C++
|
||
int main() {
|
||
std::unique_ptr<Ammunition> grapeShot = std::make_unique<GrapeShot>(
|
||
100, std::make_unique<SDLFireStrategy>(), std::make_unique<OpenGLDrawStrategy>());
|
||
|
||
grapeShot->fire();
|
||
grapeShot->draw();
|
||
}
|
||
```
|
||
|
||
```bash
|
||
SDL Grape Shot Deal: 20 damage!
|
||
OpenGL Grape Shot: 100
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.92em" -->
|
||
___
|
||
|
||
## Separate implementation
|
||
|
||
Strategy can distinguish type, but still depends on all types, let's rewrite the code once more and use another letter form SOLID: **I**nterface segregation. Each implementation will be a separate strategy class, so when we add a new type, other strategies will not know about it.
|
||
|
||
```C++
|
||
class FireChainShotStrategy {
|
||
public:
|
||
virtual void fire(const ChainShot& shot) = 0;
|
||
};
|
||
|
||
class FireRoundShotStrategy {
|
||
virtual void fire(const RoundShot& shot) = 0;
|
||
};
|
||
|
||
class FireGrapeShotStrategy {
|
||
virtual void fire(const GrapeShot& shot) = 0;
|
||
};
|
||
|
||
class SDLFireChainShotStrategy : public FireChainShotStrategy {
|
||
public:
|
||
void fire(const ChainShot& shot) override;
|
||
};
|
||
|
||
class SDLFireRoundShotStrategy : public FireRoundShotStrategy {
|
||
public:
|
||
void fire(const RoundShot& shot) override;
|
||
};
|
||
|
||
class SDLFireGrapeShotStrategy : public FireGrapeShotStrategy {
|
||
public:
|
||
void fire(const GrapeShot& shot) override;
|
||
};
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.67em" -->
|
||
___
|
||
|
||
```C++
|
||
class DrawChainShotStrategy {
|
||
public:
|
||
virtual void draw(const ChainShot& shot) = 0;
|
||
};
|
||
|
||
class DrawRoundShotStrategy {
|
||
virtual void draw(const RoundShot& shot) = 0;
|
||
};
|
||
|
||
class DrawGrapeShotStrategy {
|
||
virtual void draw(const GrapeShot& shot) = 0;
|
||
};
|
||
```
|
||
|
||
```C++
|
||
class OpnGLDrawChainShotStrategy : public DrawChainShotStrategy {
|
||
public:
|
||
void draw(const ChainShot& shot) override;
|
||
};
|
||
|
||
class OpenGLDrawRoundShotStrategy : public DrawRoundShotStrategy {
|
||
public:
|
||
void draw(const RoundShot& shot) override;
|
||
};
|
||
|
||
class OpenGLDrawGrapeShotStrategy : public DrawGrapeShotStrategy {
|
||
public:
|
||
void draw(const GrapeShot& shot) override;
|
||
};
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.78em" -->
|
||
___
|
||
|
||
## Single resposibility
|
||
|
||
In the beginning, we may think that we will need to create too many classes because each `Ammunition` type needs 4 classes (2 for draw, and 2 for fire), but when we take a look at the first letter of SOLID - **S**ingle responsibility, we understand that now we follow this rule. Previously `OpenGLDrawStrategy` and `MEtalDrawStrategy` needed to know about 3 different types of ammunition, and we broke all rules:
|
||
* <!-- .element: class="fragment fade-in" --> <b>S</b>ingle resposinility -> each class need to know about all types
|
||
* <!-- .element: class="fragment fade-in" --> <b>O</b>pen close -> code was not open for extension, when we add a new type, we need to implement behavior in all 4 classes, even if we don't want to, which may cause to break another letter:
|
||
* <!-- .element: class="fragment fade-in" --> <b>L</b>iskov substitution -> we may inherit from an interface which is not fully suitable for us, that's why we should:
|
||
* <!-- .element: class="fragment fade-in" --> <b>I</b>nterface segregation -> separate too big interface into a smaller one.
|
||
* <!-- .element: class="fragment fade-in" --> <b>D</b>ependency inversion -> High level class <code>DrawStrategy</code> nad <code>FireStrategy</code> depends on low level classes <code>RoundShot</code>, <code>ChainShot</code> and <code>GrapeShot</code>.
|
||
|
||
As you can see, the following implementation of strategy, with many small classes is the correct one.
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.87em" -->
|
||
___
|
||
|
||
## Broken Dependency inversion
|
||
|
||
<b>D</b>ependency inversion -> High level class <code>DrawStrategy</code> nad <code>FireStrategy</code> depends on low level classes <code>RoundShot</code>, <code>ChainShot</code> and <code>GrapeShot</code>.
|
||
|
||
<img data-src="images/strategy_broken_architecture.png" alt="Broken architecture">
|
||
<!-- .slide: style="font-size: 0.94em" -->
|
||
___
|
||
|
||
## Modern approach
|
||
|
||
Following the rule of SOLID, we almost reached a perfect architecture, but the one thing left. Class `DrawChainStrategy`, `DrawRoundStrategy`, and `DrawGrapeStrategy` is connected with a specific type like: `RoundShot` or `ChainShot`. We follow all SOLID rules, but still there is one thing to improve. **Do you know how we can improve it**?
|
||
|
||
<img data-src="images/strategy_good_architecture.png" alt="Good architecture">
|
||
<!-- .slide: style="font-size: 0.90em" -->
|
||
___
|
||
|
||
## Template strategy
|
||
|
||
By creating a template for `DrawStrategy` we remove all dependencies between specific types of ammunition and strategy:
|
||
|
||
```C++
|
||
template <typename T>
|
||
class DrawStrategy {
|
||
virtual ~DrawStrategy() = default;
|
||
virtual void draw(const T&) const = 0;
|
||
};
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
<img data-src="images/strategy_perfect_architecture.png" alt="Perfect architecture">
|
||
<!-- .slide: style="font-size: 0.70em" -->
|
||
<!-- .element: class="fragment fade-in" -->
|
||
___
|
||
|
||
## Full implementation
|
||
|
||
```C++
|
||
template <typename T>
|
||
class FireStrategy {
|
||
public:
|
||
virtual void fire(const T& shot) = 0;
|
||
};
|
||
|
||
class SDLFireRoundShotStrategy : public FireStrategy<RoundShot> {
|
||
public:
|
||
void fire(const RoundShot& shot) override;
|
||
};
|
||
|
||
class SDLFireChainShotStrategy : public FireStrategy<ChainShot> {
|
||
public:
|
||
void fire(const ChainShot& shot) override;
|
||
};
|
||
|
||
class SDLFireGrapeShotStrategy : public FireStrategy<GrapeShot> {
|
||
public:
|
||
void fire(const GrapeShot& shot) override;
|
||
};
|
||
```
|
||
|
||
```C++
|
||
class WAMFireRoundShotStrategy : public FireStrategy<RoundShot> {
|
||
public:
|
||
void fire(const RoundShot& shot) override;
|
||
};
|
||
|
||
class WAMFirChainShotStrategy : public FireStrategy<ChainShot> {
|
||
public:
|
||
void fire(const ChainShot& shot) override;
|
||
};
|
||
|
||
class WAMFireGrapeShotStrategy : public FireStrategy<GrapeShot> {
|
||
public:
|
||
void fire(const GrapeShot& shot) override;
|
||
};
|
||
```
|
||
<!-- .slide: style="font-size: 0.60em" -->
|
||
<!-- .element: class="fragment fade-in" -->
|
||
___
|
||
|
||
```C++
|
||
template <typename T>
|
||
class DrawStrategy {
|
||
public:
|
||
virtual void draw(const T& shot) = 0;
|
||
};
|
||
|
||
class OpenGLDrawRoundShotStrategy : public DrawStrategy<RoundShot> {
|
||
public:
|
||
void draw(const RoundShot& shot) override;
|
||
};
|
||
|
||
class OpenGLDrawChainShotStrategy : public DrawStrategy<ChainShot> {
|
||
public:
|
||
void draw(const ChainShot& shot) override;
|
||
};
|
||
|
||
class OpenGLDrawGrapeShotStrategy : public DrawStrategy<GrapeShot> {
|
||
public:
|
||
void draw(const GrapeShot& shot) override;
|
||
};
|
||
```
|
||
|
||
```C++
|
||
class MetalDrawRoundShotStrategy : public DrawStrategy<RoundShot> {
|
||
public:
|
||
void draw(const RoundShot& shot) override;
|
||
};
|
||
|
||
class MetalDrawChainShotStrategy : public DrawStrategy<ChainShot> {
|
||
public:
|
||
void draw(const ChainShot& shot) override;
|
||
};
|
||
|
||
class MetalDrawGrapeShotStrategy : public DrawStrategy<GrapeShot> {
|
||
public:
|
||
void draw(const GrapeShot& shot) override;
|
||
};
|
||
```
|
||
<!-- .slide: style="font-size: 0.60em" -->
|
||
<!-- .element: class="fragment fade-in" -->
|
||
___
|
||
|
||
```C++
|
||
class RoundShot : public Ammunition {
|
||
public:
|
||
RoundShot(size_t amount, std::unique_ptr<FireStrategy<RoundShot>>&& fireStrategy, std::unique_ptr<DrawStrategy<RoundShot>>&& drawStrategy)
|
||
: Ammunition(amount), _fireStrategy(std::move(fireStrategy)), _drawStrategy(std::move(drawStrategy)) {}
|
||
void fire() override {
|
||
_fireStrategy->fire(*this /* other args if needed*/);
|
||
}
|
||
void draw() override {
|
||
_drawStrategy->draw(*this /* other args if needed*/);
|
||
}
|
||
int getDamage() const override { return 10; }
|
||
|
||
private:
|
||
std::unique_ptr<FireStrategy<RoundShot>> _fireStrategy;
|
||
std::unique_ptr<DrawStrategy<RoundShot>> _drawStrategy;
|
||
};
|
||
```
|
||
|
||
```C++
|
||
class ChainShot : public Ammunition {
|
||
public:
|
||
ChainShot(size_t amount, std::unique_ptr<FireStrategy<ChainShot>>&& fireStrategy, std::unique_ptr<DrawStrategy<ChainShot>>&& drawStrategy)
|
||
: Ammunition(amount), _fireStrategy(std::move(fireStrategy)), _drawStrategy(std::move(drawStrategy)) {}
|
||
void fire() override {
|
||
_fireStrategy->fire(*this /* other args if needed*/);
|
||
}
|
||
void draw() override {
|
||
_drawStrategy->draw(*this /* other args if needed*/);
|
||
}
|
||
int getDamage() const override { return 15; }
|
||
|
||
private:
|
||
std::unique_ptr<FireStrategy<ChainShot>> _fireStrategy;
|
||
std::unique_ptr<DrawStrategy<ChainShot>> _drawStrategy;
|
||
};
|
||
```
|
||
<!-- .slide: style="font-size: 0.60em" -->
|
||
<!-- .element: class="fragment fade-in" -->
|
||
___
|
||
|
||
```C++
|
||
class GrapeShot : public Ammunition {
|
||
public:
|
||
GrapeShot(size_t amount, std::unique_ptr<FireStrategy<GrapeShot>>&& fireStrategy, std::unique_ptr<DrawStrategy<GrapeShot>>&& drawStrategy)
|
||
: Ammunition(amount), _fireStrategy(std::move(fireStrategy)), _drawStrategy(std::move(drawStrategy)) {}
|
||
void fire() override {
|
||
_fireStrategy->fire(*this /* other args if needed*/);
|
||
}
|
||
void draw() override {
|
||
_drawStrategy->draw(*this /* other args if needed*/);
|
||
}
|
||
int getDamage() const override { return 20; }
|
||
|
||
private:
|
||
std::unique_ptr<FireStrategy<GrapeShot>> _fireStrategy;
|
||
std::unique_ptr<DrawStrategy<GrapeShot>> _drawStrategy;
|
||
};
|
||
```
|
||
|
||
```C++
|
||
int main() {
|
||
std::unique_ptr<Ammunition> grapeShot = std::make_unique<GrapeShot>(
|
||
100, std::make_unique<SDLFireGrapeShotStrategy>(), std::make_unique<OpenGLDrawGrapeShotStrategy>());
|
||
|
||
grapeShot->fire();
|
||
grapeShot->draw();
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
```bash
|
||
SDL Grape Shot Deal: 20 damage!
|
||
OpenGL Grape Shot: 100
|
||
```
|
||
<!-- .slide: style="font-size: 0.60em" -->
|
||
<!-- .element: class="fragment fade-in" -->
|
||
___
|
||
|
||
## Exercise 1
|
||
|
||
* <!-- .element: class="fragment fade-in" --> Go into directory <code>Core</code> and implement <code>ShipStrategy</code> as a template class
|
||
* <!-- .element: class="fragment fade-in" --> Strategy should have one pure virtual function: <code>Res handle(T&)</code>
|
||
* <!-- .element: class="fragment fade-in" --> Implement in directory <code>Ship</code> class <code>ShipEasyDifficultLvlStrategy</code>
|
||
* <!-- .element: class="fragment fade-in" --> Each cargo supplies 2 sailors, so if you have 40 crew, you need 20 banas and 20 rum
|
||
* <!-- .element: class="fragment fade-in" --> Implement <code>NextDay</code> by using strategy class
|
||
* <!-- .element: class="fragment fade-in" --> Run code, and verify result
|
||
|
||
___
|
||
|
||
## Exercise 2
|
||
|
||
* <!-- .element: class="fragment fade-in" --> Implement in directory *Player* class *PalyerEasyDifficultLvlStrategy*
|
||
* <!-- .element: class="fragment fade-in" --> Strategy should roll a die (1-20):
|
||
* <!-- .element: class="fragment fade-in" --> If result is lower than 5 -> 0 DMG
|
||
* <!-- .element: class="fragment fade-in" --> If result is higher than 19 -> multiple damage by 2
|
||
* <!-- .element: class="fragment fade-in" --> Damage should be rolled from (25 to 50)
|
||
* <!-- .element: class="fragment fade-in" --> deal damage to the ship and return how much damage was deal. Print it in console
|
||
* <!-- .element: class="fragment fade-in" --> Add all necessary implementation to the *Enemy* file
|
||
* <!-- .element: class="fragment fade-in" --> Test it
|
||
|
||
___
|
||
|
||
## Exercise 3
|
||
|
||
* <!-- .element: class="fragment fade-in" --> Implement new classes *EnemyHardDifficultLvlStrategy* and *ShipHardDifficultLvlStrategy*
|
||
* <!-- .element: class="fragment fade-in" --> Strategy for Ship, should subtract cargo equal crew size
|
||
* <!-- .element: class="fragment fade-in" --> Strategy for Enemy should roll a die (1-20):
|
||
* <!-- .element: class="fragment fade-in" --> If result is lower than 3 -> 0 DMG
|
||
* <!-- .element: class="fragment fade-in" --> If result is higher than 15 but not equal 20 -> multiple damage by 3
|
||
* <!-- .element: class="fragment fade-in" --> If result is higher than 19 -> multiple damage by 3
|
||
* <!-- .element: class="fragment fade-in" --> Damage should be rolled from (30 to 60)
|
||
* <!-- .element: class="fragment fade-in" --> You shouldn't modify any existing file, except the main.cpp, where you should use a new strategy
|
||
* <!-- .element: class="fragment fade-in" --> Run code and verify the result
|
||
<!-- .slide: style="font-size: 0.95em" -->
|
||
___
|
||
|
||
## Strategy is everywhere
|
||
|
||
Before we go on to the next design pattern. I want to show you that `Strategy` is almost everywhere. Let's closely look at some STL algorithms:
|
||
|
||
```C++
|
||
template< class RandomIt >
|
||
void sort( RandomIt first, RandomIt last );
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
```C++
|
||
template< class RandomIt, class Compare >
|
||
void nth_element( RandomIt first, RandomIt nth, RandomIt last, Compare comp );
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
```C++
|
||
template< class InputIt1, class InputIt2, class OutputIt, class Compare >
|
||
OutputIt set_difference( InputIt1 first1, InputIt1 last1,
|
||
InputIt2 first2, InputIt2 last2,
|
||
OutputIt d_first, Compare comp );
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
**What is common for all of these functions?**
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
<!-- .slide: style="font-size: 0.82em" -->
|
||
___
|
||
|
||
STL library was designed by using design patterns, which gives this library a lot of flexibility. Users who want to use one of the STL algorithms, need to provide a strategy, mostly it will be a lambda object, which tells the algorithm how to treat a specific object. What's more, it uses modern by-value semantics, because we don't have any polymorphisms and virtual functions. The first version of STL was designed in 1994-1998, so this modern approach has 30 years :) I think we should learn a lot from this design, because not always need to solve everything by creating virtual classes.
|
||
|
||
___
|
||
|
||
## Rewrite Difficult Strategy
|
||
|
||
```C++
|
||
template <typename DifficultLvlStrategy>
|
||
class Ship : public TimeObserver {
|
||
public:
|
||
Ship(Time* time, DifficultLvlStrategy strategy, const std::string& name, int capacity, int crew);
|
||
// ohter public methods
|
||
|
||
private:
|
||
// other members
|
||
DifficultLvlStrategy _strategy;
|
||
};
|
||
```
|
||
|
||
```C++
|
||
int main() {
|
||
auto strategy = [](Ship& ship[]){
|
||
Ship::StatusCode status;
|
||
ship.unload("Rum", ship.crew() / 2, status);
|
||
Ship::StatusCode status2;
|
||
ship.unload("Banana", ship.crew() / 2, status2);
|
||
|
||
return status == Ship::StatusCode::OK && status2 == Ship::StatusCode::OK;
|
||
};
|
||
|
||
Ship ship(&time, strategy, "Black Widow", 1000, 40);
|
||
}
|
||
```
|
||
<!-- .slide: style="font-size: 0.82em" -->
|
||
|
||
___
|
||
|
||
## If we don't like templates
|
||
|
||
```C++
|
||
class Ship : public TimeObserver {
|
||
public:
|
||
Ship(Time* time, std::function<size_t()> strategy, const std::string& name, int capacity, int crew);
|
||
// ohter public methods
|
||
|
||
private:
|
||
// other members
|
||
std::function<size_t(Ship&)> _strategy;
|
||
};
|
||
```
|
||
|
||
```C++
|
||
int main() {
|
||
auto strategy = [](Ship& ship[]){
|
||
Ship::StatusCode status;
|
||
ship.unload("Rum", ship.crew() / 2, status);
|
||
Ship::StatusCode status2;
|
||
ship.unload("Banana", ship.crew() / 2, status2);
|
||
|
||
return status == Ship::StatusCode::OK && status2 == Ship::StatusCode::OK;
|
||
};
|
||
|
||
Ship ship(&time,strategy, "Black Widow", 1000, 40);
|
||
}
|
||
```
|
||
<!-- .slide: style="font-size: 0.80em" -->
|