Lazy visitor. Modification of the visitor design pattern and downcasting without RTTI.
Dear reader.
I hope this finds you well. I want to introduce your modification for the classic Visitor pattern to make usage of this pattern more agile and easier. I’ve called this modification - lazy visitor. Culmination of my topic will be an example for making downcasting without RTTI, based on the lazy visitor.
First of all, we will make a revision of original visitor pattern.
What features does this pattern give us?
You can assign some operations, for any class in the class hierarchy, without changing signature of classes from the hierarchy.
You can restore lost information from pointer to the base class.
You can determinate special behavior for certain derived classes, having only pointer to the base class.
Provides double dispatch mechanism.
We have a class hierarchy for different shapes. Each derived class has a unique internal data to describe position, such as: width, height, radius etc. Let’s imagine that we have unexpectedly decided, that we need found the way how to represent information about this classes to the console, window, or save to the file. If we want to avoid ruining classic class hierarchy and open a way for adding new functionality, we can use Visitor pattern to solve this problem.
This an example shows how to use the visitor patter. Class ConsoleVisitor appends new operation to the whole Shape class hierarchy. Each method “visit” in the visitor class describes certain behavior for certain derived class.
Now, if you want to define personal behavior for each derived class without changing class hierarchy, you just need to write new visitor and pass it to the accept method.
But, visitor has well-known drawbacks:
If you add to the target class hierarchy new class, you have to change all visitors as well ( new class = additional “visit” method).
If I want to add special behavior only for one derived class, I have to write new visitor with the implementation only for the one “visit” method. That is redundantly.
Result extraction from the visitor after work with several derived classes to combine results is not convenient. For instance: when you want to combine coordinates for square and triangle by using the visitor pattern.
How to avoid all these drawbacks and, as a bonus, have ability to make easy downcasting?
To make visitor agile, we can replace simple method calling to callback, calling using lambda expressions, functors, or direct function calling:
As you can see we can set any behavior for any derived class using lambda expression, and it is really suitable.
The next example shows how to get individual information for every derived class.
We set lambda expression for each interested type. If actual type of shape is Circle, lambda expression for Circle type will be called etc.
We also can create new visitor only to manage only the one derived class with any behavior.
We can create numerous visitors with different behaviors emplace, without visitor class declarations.
But, we still have many of duplicated codes in the lazy visitor. We can solve this problem by using template version of the lazy visitor with std::tuple.
In this version, we will have separate components for each derived class. Each component can set callback function and call the “visit” method for certain derived class.
Using this approach, the role of the lazy visitor class is shrink to the collection of visitor components, declared on the compile time.
Now you can use TVisitor instead of visitor class.
In conclusion, as I have promised, we can write downcast function for any class hierarchy which is using lazy visitor.