G2Labs Grzegorz Grzęda
Visitor design pattern
May 21, 2023
When we have a collection of concrete classes and we want to perform some operations, we usually need to manually invoke each method ‘by hand’. Usually we create a common interface and place methods to be implemented. Them we can invoke those methods in a more generic fashion.
But this approach introduces threats to the project. Mainly, we force all classes to implement some methods that not necessarily align with all of those classes logic. This lie has to propagate throughout the architecture.
There is a solution to this. We can invert the direction of control and introduce a class that is specifically designed to handle incompatible (by type) objects that need to be handled closely. This is the crux of the Visitor Design Pattern.
We indeed create a common interface, but only to deliver a method, through which the object in question would need to accept
a visitor. It is up to the visitor, what it would do, when the invitation would be accepted.
The project
We will create a simple project using the list component from one of earlier posts.
We will have three types of objects: a Doer
, Thinker
and Writer
. Each will perform specific operations, uncommon for each other. Yet, each will have to implement a IElement
interface, where each could accept
a IVisitor
. All will be gathered under a List
, so that the visitor could conveniently visit each object.
Here, the IElement
has a method for accepting a visitor. Nothing fancy so far.
This starts to look interesting! We declare an IVisitor
type and say, that somewhere there in the code, there are some structure types we would like to use. We cannot write #include "Doer.hpp
and so on.
Why? Because in Doer.hpp
there will be a #include "IElement.hpp"
. In IElement.hpp
there is a #include "IVisitor.hpp"
. In each header file we’ve said #pragma once
, so the compiler would not include further. We would get an infinite inclusion loop.
By just saying struct Doer;
we mean “somewhere there is a Doer. Don’t worry, the linker will find it where it is”.
Back to the project…
Notice, that in each element we accept the visitor and point to the accepting object. We don’t know what, how, or when the visitor will be doing anything to that particular object. We just accept it. It is up to the visitor what to do.
|
|
The GuestVisitor
is quite simple. Depending on the type of object it visits, it performs different tasks.
In the main
function we can see the beauty of the solution:
|
|
We create a list of objects. The only commonality is the implementation of the IElement
. Next we create some Visitor and visit each element in a loop fashion.
Conclusion
The Visitor DP gives us two things:
- Releases us from the hustle of complicated cross-object method invocation. From now on, there is a visitor, who curates the visiting process,
- Gathers collective object control into a specific location. What would normally be done by a pile of
switch
cases raging all around the project, now is one, two classes with defined scope and purpose.
Ways of expansion
Go on and experiment with these examples! You can:
- expand each concrete class with other specific methods,
- add another visitor with different
rules of engagement
.