# Visitor Another popular design pattern is **Visitor**. As previously, I'm certain that You also use it at least once, but maybe you didn't know about it. Let's check the following STL algorithm: ```C++ class VariantVisitor { public: void operator()(int num) const { std::cout << "int: " << num << "\n"; } void operator()(std::string str) const { std::cout << "string: " << str << "\n"; } }; int main() { std::variant variant; variant = "Ala has a cat"; // print: string: Ala has a cat std::visit(VariantVisitor{}, variant); } ``` **Seems familiar? If not, let's check another example** ___ ```C++ class Shape { public: virtual ~Shape() = default; virtual void accept( ShapeVisitor const& v ) = 0; 3 }; class Circle : public Shape { public: explicit Circle( double radius ): radius_( radius ) {} void accept( ShapeVisitor const& v ) override { v.visit( *this ); } double radius() const { return radius_; } private: double radius_; }; class Square : public Shape { public: explicit Square( double side ): side_( side ) {} void accept( ShapeVisitor const& v ) override { v.visit( *this ); } double side() const { return side_; } private: double side_; }; ``` ```C++ class Draw : public ShapeVisitor { public: void visit( Circle const& c, /*...*/ ) const override; void visit( Square const& s, /*...*/ ) const override; }; class Translate : public ShapeVisitor { public: void visit( Circle const& c, /*...*/ ) const override; void visit( Square const& s, /*...*/ ) const override; }; ``` ___ ## Visitor These two examples were two different implementations of the same design pattern which is `Visitor`. In short, the visitor is used to dispatch functions between a few types. Visitor UML ___ ## Example Let's back to the pirates. In the previous example, we implemented the ammunition class and added an implementation of `fire` and `draw` methods, so what next? We need to add also `methods` that will calculate how much damage each ammunition does and what sound should be played. Visitor first apprach ___ ## Drawback In the first approach, we can see that we tightly coupled the base class `Visitor` with a base class `Ammunition` and the implementation of `Visitor` with types of `Ammunition`. So we have a lot of dependencies. Unfortunately, the visitor is a nice pattern, but it is good only for extending functions, but it is closed for adding new types because when we add a new type of ammunition we also need to implement every visitor class. There is a solution for this, but it is inefficient, and can't be always used. I will talk about this solution later. For now, it should be enough to know that there is sth like `Acyclic visitor` that can solve this problem, but it is inefficient. In this solution, we have double dispatch and the compiler can't optimize such code, also we have to call 2 or 3 virtual functions which are not as fast as calling normal functions. ___ ## Implementation let's start by defining an interface for both, the `Ammunition` and `Visitor` class. ```C++ class Ammunition { public: Ammunition(size_t amount) : _amount(amount) {} virtual ~Ammunition() = default; virtual void accept(const AmmunitionVisitor&) = 0; size_t amount() const { return _amount; } private: size_t _amount = 0; }; ``` ```C++ class AmmunitionVisitor { public: virtual ~AmmunitionVisitor() = default; virtual void visit(const RoundShot&) const = 0; virtual void visit(const ChainShot&) const = 0; virtual void visit(const GrapeShot&) const = 0; }; ``` ___ Each class that is derived from `Ammunition` needs to create a definition of the `accept` method. The is needed because each class passes a pointer to `*this` which allows us to distinguish which inherited class was provided. ```C++ class RoundShot : public Ammunition { public: using Ammunition::Ammunition; void accept(const AmmunitionVisitor& visitor) { visitor.visit(*this); } }; class ChainShot : public Ammunition { public: using Ammunition::Ammunition; void accept(const AmmunitionVisitor& visitor) { visitor.visit(*this); } }; class GrapeShot : public Ammunition { public: using Ammunition::Ammunition; void accept(const AmmunitionVisitor& visitor) { visitor.visit(*this); } }; ``` ___ At the end we provide implementation for each visitor ```C++ class DamageVisitor : public AmmunitionVisitor { public: void visit(const RoundShot& shot) const override { std::cout << "Round shot make 20 damages\n"; } void visit(const ChainShot& shot) const override { std::cout << "Chain shot make 30 damages\n"; } void visit(const GrapeShot& shot) const override { std::cout << "Grape shot make 40 damages\n"; } }; class SoundVisitor : public AmmunitionVisitor { public: void visit(const RoundShot& shot) const override { std::cout << "Fiuuuuuuuu Bam!\n"; } void visit(const ChainShot& shot) const override { std::cout << "Fiuuuuuuuuuuu Bam bam!\n"; } void visit(const GrapeShot& shot) const override { std::cout << "Fiuuuuuuuuuuu Bam bam bam bam bam!\n"; } }; ``` ___ ## Usage ```C++ int main() { std::unique_ptr grapeShot = std::make_unique(100); SoundVisitor soundVisitor; DamageVisitor damageVisitor; grapeShot->accept(soundVisitor); grapeShot->accept(damageVisitor); } ``` ```bash Fiuuuuuuuuuuu Bam bam bam bam bam! Grape shot make 40 damages ``` ___ ## Drawbacks Visitor in this approach has 4 drawbacks: * First I mentioned earlier it will be hard to add a new type, we can solve it by using acyclic visitor * Visitor is tightly coupled with other classes and not elastic. For instance, DamageVisitor implementations may be similar, but we need to implement a method for every class. We can separate common parts to another function, but still, it will be great if we can have one function for all types if needed. By using visitors, this is not possible * If we want to introduce visitors in an already implemented hierarchy of classes we need to add the accept method to every base class, sometimes we don't have access to all files in the repo, or the repository is divided and it is not possible. There is a solution for this, which I will show you later * If we add a new class that inherits from an already existing type, and we forget to override method, we will have the same behavior as the class that we inherit, which is a huge bug! ___ ## Improvements As I said a few times, the modern approach uses by-value semantics, instead of virtual functions. We can use static or compile-time polymorphism to achieve the same and have even better performance. let's rewrite this example to use `std::variant` which allows us to decouple classes, and make a code more flexible and even faster (I will show you a benchmark later). ```C++ class Ammunition { public: Ammunition(size_t amount) : _amount(amount) {} virtual ~Ammunition() = default; size_t amount() const { return _amount; } private: size_t _amount = 0; }; class RoundShot final : public Ammunition { public: using Ammunition::Ammunition; }; class ChainShot final : public Ammunition { public: using Ammunition::Ammunition; }; class GrapeShot final : public Ammunition { public: using Ammunition::Ammunition; }; ``` ___ ```C++ class DamageVisitor { public: void operator()(const RoundShot& shot) const { std::cout << "Round shot make 20 damages\n"; } void operator()(const ChainShot& shot) const { std::cout << "Chain shot make 30 damages\n"; } void operator()(const GrapeShot& shot) const { std::cout << "Grape shot make 40 damages\n"; } }; class SoundVisitor { public: void operator()(const RoundShot& shot) const { std::cout << "Fiuuuuuuuu Bam!\n"; } void operator()(const ChainShot& shot) const { std::cout << "Fiuuuuuuuuuuu Bam bam!\n"; } void operator()(const GrapeShot& shot) const { std::cout << "Fiuuuuuuuuuuu Bam bam bam bam bam!\n"; } }; ``` ___ We get rid of a lot of virtual functions and simplify the code. ```C++ int main() { using Shot = std::variant; Shot shot = GrapeShot(100); SoundVisitor soundVisitor; DamageVisitor damageVisitor; std::visit(damageVisitor, shot); std::visit(soundVisitor, shot); } ``` ```bash Grape shot make 40 damages Fiuuuuuuuuuuu Bam bam bam bam bam! ``` ___ ## Benchmark I created a cimple bechmark to show you, that visitors based on variant may be faster. It depends on how heavy the is function visit, if we perform a fast operation, we will gain a lot, but if the operation is heavy we will barely see the difference between both solutions. Visitor benchamrk ___ ## Simplified class hierarchy Visitor better design ___ ## Drawback std::variant We improved our code, but still, one problem exists, we can't easily add new types. The variant also provides another drawback: When types differ in size a lot, we will waste a lot of memory. In such a case we may think about using `std::unique_ptr` but this will force us to use dynamic allocation, so we lost some improvements (not fully use by-value semantic). Last things left: How to eliminate this problem by adding a new type? Let's check the `acyclic variant` class: ___ ## Acyclic visitor Visitor acyclic ___ ## Implementation First, we create an abstract visitor class and three types of visitor for each type. This allows us to cut dependency between each visitor, because each visitor has knowledge about only one ammunition type. ```C++ class AbstractVisitor { protected: // Can be only use by derived class virtual ~AbstractVisitor() = default; }; class RoundShotVisitor { public: virtual void visit(const RoundShot& shot) const = 0; protected: virtual ~RoundShotVisitor() = default; }; class GrapeShotVisitor { public: virtual void visit(const GrapeShot& shot) const = 0; protected: virtual ~GrapeShotVisitor() = default; }; class ChainShotVisitor { public: virtual void visit(const ChainShot& shot) const = 0; protected: virtual ~ChainShotVisitor() = default; }; ``` ___ Next, we create dedicated visitors for `damage` and for `sound`. To hide implementations detail, each class inherit also from `AbstractVisitor`. This allows to use any visitor as pointer to this abstract class. ```C++ class DamageVisitor : public AbstractVisitor, public RoundShotVisitor, public GrapeShotVisitor, public ChainShotVisitor { public: void visit(const RoundShot& shot) const override { std::cout << "Round shot make 20 damages\n"; } void visit(const ChainShot& shot) const override { std::cout << "Chain shot make 30 damages\n"; } void visit(const GrapeShot& shot) const override { std::cout << "Grape shot make 40 damages\n"; } }; class SoundVisitor : public AbstractVisitor, public RoundShotVisitor, public GrapeShotVisitor, public ChainShotVisitor { public: void visit(const RoundShot& shot) const override { std::cout << "Fiuuuuuuuu Bam!\n"; } void visit(const ChainShot& shot) const override { std::cout << "Fiuuuuuuuuuuu Bam bam!\n"; } void visit(const GrapeShot& shot) const override { std::cout << "Fiuuuuuuuuuuu Bam bam bam bam bam!\n"; } }; ``` ___ For now, everything looks ok. But unfortunately, because `AbstracVisitor` has empty interface, we need to cast visitor to a specific type. This `dynamic_cast` cost a lot and make code harder to optimize. That's why this visitor is much slower than previous implementations. ```C++ class RoundShot : public Ammunition { public: using Ammunition::Ammunition; void accept(const AbstractVisitor& visitor) { if (const auto* v = dynamic_cast(&visitor)) { v->visit(*this); } } }; class ChainShot : public Ammunition { public: using Ammunition::Ammunition; void accept(const AbstractVisitor& visitor) { if (const auto* v = dynamic_cast(&visitor)) { v->visit(*this); } } }; class GrapeShot : public Ammunition { public: using Ammunition::Ammunition; void accept(const AbstractVisitor& visitor) { if (const auto* v = dynamic_cast(&visitor)) { v->visit(*this); } } }; ``` ___ ## Usage The usage of this code doesn't change in comparison to the first implementation. But we lost on efficiency. ```C++ int main() { std::unique_ptr grapeShot = std::make_unique(100); SoundVisitor soundVisitor; DamageVisitor damageVisitor; grapeShot->accept(soundVisitor); grapeShot->accept(damageVisitor); } ``` ```bash Fiuuuuuuuuuuu Bam bam bam bam bam! Grape shot make 40 damages ``` ___ ## Lost efficiency Acyclic visitor benchmark ___ ## Improvement When visitor operations are quick, better to avoid acyclic visitor to not make the code slower. But when the operations are heavy, the difference will be slightly visible, and we will decouple visitor and ammunition classes. There is also one improvement, that make a code easier to extend. Instead of writing every time a visitor for a type, we can use template. ```C++ template class Visitor { public: virtual void visit(const T& shot) const = 0; protected: virtual ~Visitor() = default; }; class DamageVisitor : public AbstractVisitor, public Visitor, public Visitor, public Visitor { public: void visit(const RoundShot& shot) const override { std::cout << "Round shot make 20 damages\n"; } void visit(const ChainShot& shot) const override { std::cout << "Chain shot make 30 damages\n"; } void visit(const GrapeShot& shot) const override { std::cout << "Grape shot make 40 damages\n"; } }; ``` ___ ```C++ class RoundShot : public Ammunition { public: using Ammunition::Ammunition; void accept(const AbstractVisitor& visitor) { if (const auto* v = dynamic_cast*>(&visitor)) { v->visit(*this); } } }; class ChainShot : public Ammunition { public: using Ammunition::Ammunition; void accept(const AbstractVisitor& visitor) { if (const auto* v = dynamic_cast*>(&visitor)) { v->visit(*this); } } }; class GrapeShot : public Ammunition { public: using Ammunition::Ammunition; void accept(const AbstractVisitor& visitor) { if (const auto* v = dynamic_cast*>(&visitor)) { v->visit(*this); } } }; ``` ___ ## UML Acyclic visitor UML ___ ## Exericse 1 * Go into directory Core and implement a base class PrintVisitor which should implement visit method for two objects: Ship and Store * Create PrettyPrintVisitor which will print cargo from Ship and Sotore (just move the implementation from printCargo). * Add the rest of necessary implementation for Ship and Store. Visitor should be taken as std::unique_ptr * Verify in the main.cpp if cargo print in the same way as previously. ___ ## Exercise 2 * Create another visitor: BasicPrintVisitor which should print the cargo but without special frames, just raw info. * For Ship print Name and Amount * For Store print Name Amount and Price * You shouldn't modify anything in your code, just change the visitor in main.cpp and you should get a new output ___ ## Exercise 3 * Create CargoDamageVisitor. You don't need to create a base class, because we know that we will not extend this code, * You should create three operator() one for each cargo type: Alcohol, Fruit, Item, * For fruit, you should draw a number between 0 and 5 and subtract such an amount of cargo * For alcohol, you should draw a number between 0 and 10 and subtract half an amount of cargo * For item, you should draw a number between 0 and 20 and subtract one quarter of cargo * Visitor should be taken as a template argument by Player class * Apply visitor whenever you successfully deal damage to ship * Check in the main class if ship lost cargo after get damaged. ___ ## Summary As you can see visitor is a very useful pattern to add additional functionality to your class, in such was you don't need to modify already existing code, instead you can inherit from visitor and create totally new functionality. You can also use `std::variant` if you want to avoid modify original class (by adding accept method) or when you can't modify class (for instance it is in separate repo). Version, with variant, is also more flexible and faster than traditional implementation. If you really want to break all dependencies between visitor class and subject, you can use acyclic visitor, but this solution is less efficient, so you need to be sure you can accept this in your code.