I keep seeing references to the visitor pattern in blogs but I've got to admit, I just don't get it. I read the wikipedia article for the pattern and I understand its mechanics but I'm still confused as to when I'd use it.
As someone who just recently really got the decorator pattern and is now seeing uses for it absolutely everywhere I'd like to be able to really understand intuitively this seemingly handy pattern as well.
Double dispatch is just one reason among others to use this pattern.
But note that it is the single way to implement double or more dispatch in languages that uses a single dispatch paradigm.
Here are reasons to use the pattern :
1) We want to define new operations without changing the model at each time because the model doesn’t change often wile operations change frequently.
2) We don't want to couple model and behavior because we want to have a reusable model in multiple applications or we want to have an extensible model that allow client classes to define their behaviors with their own classes.
3) We have common operations that depend on the concrete type of the model but we don’t want to implement the logic in each subclass as that would explode common logic in multiple classes and so in multiple places.
4) We are using a domain model design and model classes of the same hierarchy perform too many distinct things that could be gathered somewhere else.
5) We need a double dispatch.
We have variables declared with interface types and we want to be able to process them according their runtime type … of course without using
if (myObj instanceof Foo) {}
or any trick.The idea is for example to pass these variables to methods that declares a concrete type of the interface as parameter to apply a specific processing. This way of doing is not possible out of the box with languages relies on a single-dispatch because the chosen invoked at runtime depends only on the runtime type of the receiver.
Note that in Java, the method (signature) to call is chosen at compile time and it depends on the declared type of the parameters, not their runtime type.
The last point that is a reason to use the visitor is also a consequence because as you implement the visitor (of course for languages that doesn’t support multiple dispatch), you necessarily need to introduce a double dispatch implementation.
Note that the traversal of elements (iteration) to apply the visitor on each one is not a reason to use the pattern.
You use the pattern because you split model and processing.
And by using the pattern, you benefit in addition from an iterator ability.
This ability is very powerful and goes beyond iteration on common type with a specific method as
accept()
is a generic method.It is a special use case. So I will put that to one side.
Example in Java
I will illustrate the added value of the pattern with a chess example where we would like to define processing as player requests a piece moving.
Without the visitor pattern use, we could define piece moving behaviors directly in the pieces subclasses.
We could have for example a
Piece
interface such as :Each Piece subclass would implement it such as :
And the same thing for all Piece subclasses.
Here is a diagram class that illustrates this design :
This approach presents three important drawbacks :
– behaviors such as
performMove()
orcomputeIfKingCheck()
will very probably use common logic.For example whatever the concrete
Piece
,performMove()
will finally set the current piece to a specific location and potentially takes the opponent piece.Splitting related behaviors in multiple classes instead of gathering them defeats in a some way the single responsibility pattern. Making their maintainability harder.
– processing as
checkMoveValidity()
should not be something that thePiece
subclasses may see or change.It is check that goes beyond human or computer actions. This check is performed at each action requested by a player to ensure that the requested piece move is valid.
So we even don’t want to provide that in the
Piece
interface.– In chess games challenging for bot developers, generally the application provides a standard API (
Piece
interfaces, subclasses, Board, common behaviors, etc…) and let developers enrich their bot strategy.To be able to do that, we have to propose a model where data and behaviors are not tightly coupled in the
Piece
implementations.So let’s go to use the visitor pattern !
We have two kinds of structure :
– the model classes that accept to be visited (the pieces)
– the visitors that visit them (moving operations)
Here is a class diagram that illustrates the pattern :
In the upper part we have the visitors and in the lower part we have the model classes.
Here is the
PieceMovingVisitor
interface (behavior specified for each kind ofPiece
) :The Piece is defined now :
Its key method is :
It provides the first dispatch : a invocation based on the
Piece
receiver.At compile time, the method is bound to the
accept()
method of the Piece interface and at runtime, the bounded method will be invoked on the runtimePiece
class.And it is the
accept()
method implementation that will perform a second dispatch.Indeed, each
Piece
subclass that wants to be visited by aPieceMovingVisitor
object invokes thePieceMovingVisitor.visit()
method by passing as argument itself.In this way, the compiler bounds as soon as the compile time, the type of the declared parameter with the concrete type.
There is the second dispatch.
Here is the
Bishop
subclass that illustrates that :And here an usage example :
Visitor drawbacks
The Visitor pattern is a very powerful pattern but it also has some important limitations that you should consider before using it.
1) Risk to reduce/break the encapsulation
In some kinds of operation, the visitor pattern may reduce or break the encapsulation of domain objects.
For example, as the
MovePerformingVisitor
class needs to set the coordinates of the actual piece, thePiece
interface has to provide a way to do that :The responsibility of
Piece
coordinates changes is now open to other classes thanPiece
subclasses.Moving the processing performed by the visitor in the
Piece
subclasses is not an option either.It will indeed create another issue as the
Piece.accept()
accepts any visitor implementation. It doesn't know what the visitor performs and so no idea about whether and how to change the Piece state.A way to identify the visitor would be to perform a post processing in
Piece.accept()
according to the visitor implementation. It would be a very bad idea as it would create a high coupling between Visitor implementations and Piece subclasses and besides it would probably require to use trick asgetClass()
,instanceof
or any marker identifying the Visitor implementation.2) Requirement to change the model
Contrary to some other behavioral design patterns as
Decorator
for example, the visitor pattern is intrusive.We indeed need to modify the initial receiver class to provide an
accept()
method to accept to be visited.We didn't have any issue for
Piece
and its subclasses as these are our classes.In built-in or third party classes, things are not so easy.
We need to wrap or inherit (if we can) them to add the
accept()
method.3) Indirections
The pattern creates multiples indirections.
The double dispatch means two invocations instead of a single one :
And we could have additional indirections as the visitor changes the visited object state.
It may look like a cycle :
One way to look at it is that the visitor pattern is a way of letting your clients add additional methods to all of your classes in a particular class hierarchy.
It is useful when you have a fairly stable class hierarchy, but you have changing requirements of what needs to be done with that hierarchy.
The classic example is for compilers and the like. An Abstract Syntax Tree (AST) can accurately define the structure of the programming language, but the operations you might want to do on the AST will change as your project advances: code-generators, pretty-printers, debuggers, complexity metrics analysis.
Without the Visitor Pattern, every time a developer wanted to add a new feature, they would need to add that method to every feature in the base class. This is particularly hard when the base classes appear in a separate library, or are produced by a separate team.
(I have heard it argued that the Visitor pattern is in conflict with good OO practices, because it moves the operations of the data away from the data. The Visitor pattern is useful in precisely the situation that the normal OO practices fail.)
While I have understood the how and when, I have never understood the why. In case it helps anyone with a background in a language like C++, you want to read this very carefully.
For the lazy, we use the visitor pattern because "while virtual functions are dispatched dynamically in C++, function overloading is done statically".
Or, put another way, to make sure that CollideWith(ApolloSpacecraft&) is called when you pass in a SpaceShip reference that is actually bound to an ApolloSpacecraft object.