So I opened the door and this person wanted to provide me with a service.
I told him “Look, I moved in here in 2021, its taken me ages to get everything in here just how I like it and really don't want you messing about with anything just to provide me with whatever it is you’re selling so no thank you.”
But they insisted with just a small addition where they could sit (not in my favourite chair mind you) then I would not need to make any huge changes with the added bonus that if I made this change, then other people could come visit and I’d be able to use their services. After some persistence I accepted them into my home.
OK maybe that was a bit of a narrative, but as many of the visitor pattern articles I’ve digested over these past couple of weeks have just gone straight into the technical aspect of it, I wanted to set the scene with a story before getting into technicalities.
Into the nitty gritty as it were. There will be times when it will be necessary to extend a class, after all we want to keep things open for extension but closed for modification.
Dispatch & Double Dispatch (a.k.a. What?)
Dispatch is straight polymorphism. I’ll go for the classic Animal abstract base class as an example with an abstract function named Speak. It has two derived classes, Dog and Cat. When we ask the dog to speak, yes the dog goes “woof”. If we ask the cat, it ignores us (I’m not a cat person for that reason). Ok no it doesn’t, it goes “miaow”
Double dispatch, polymorphism on steroids. Ok maybe not, think of it as two way communication. The Dog object receives the visitor via the Accept function as shown in the listing below and then it passes itself right back to the visitor which then does whatever its function is needing to do as it has overloaded Visit functions for each possible type it can deal with.
Can’t we just create an extension method?
This is definitely possible but it just kicks the can down the road and definitely messes up the Open closed principle if there are multiple concrete types we’d need to check.
Also, what if there are private fields in the class which we need to access? Herein lies the rub, an extension method wont have access to that information and neither will the visitor pattern. You may well be forced to make those private fields, public.
How's about we build up a small application stage by stage to help get the concept home. Lets start with the basic “pre-visitor” solution.
Making the type “visitable”
Ok, sure we said that this pattern would allow us to add functionality without playing around with the internals, but there has to be a way in for the visitor itself.
We have our animals and some basic information about them. But then realised we don't have a way to see how old they are. We don’t want to add a function to calculate their age but we can create a visitor that does it for us.
The visitor does need access to the DateOfBirth though so we have to make that publicly accessible, (or provide a public accessor to it).
Because the visitor in this instance does not return anything, but we are interested in the result, we do need to store the result in there resulting in the visitor maintaining state.
What if we want it to not just do something but also return something?
Then that would involve a slightly different flavour of visitor pattern which can be called a reduction or transformer. Whats good about this is the visitor does not need to maintain state if the calling function wanted to see any potential output.
The only limitation is that everything in that visitor returns the same type. While you can specify the type being returned, I’ve found it friendlier and a bit more flexible to use a generic interface.
A little c-sharp example:
So thats the setup. We have a base class of Animal, two derived classes of Dog and Cat and each of those accepts a visitor that implements IAnimalVisitor.
How to put it all together? Its not that bad.
The output from the above will be:
So the dog type Accepts the visitor then immediately passes itself back to the visitor and the visitor performs whatever operation it does on the type through its overloaded Visit(….) function.
Obviously no design pattern is without its “cons”. In this instance if we had a collection of visitors already implemented and each one works on the Dog and Cat type, if we were to add a GuineaPig type in the future, we’d have to update each concrete implementation of the visitor.