The visitor behavioral pattern allows you to perform actions on a collection of different types where the actions depend on the types.
This pattern represents an operation that can be performed on a collection of objects and allows new operations to be defined without changing the objects. Because all the items in a collection or in a composite need to share a common interface, from outside the collection, they all appear to be the same. If you have many operations that you want to perform on the objects in the collection and want this operations to be customized by the type of object, then you might start out by adding all these operations as virtual methods in the interface. This leads to a very complicated and wasteful design when you have many different operations. And as new operations are added, the interface and each of the object types need to change to support the new operation.
This interface works best when the type of objects in the collection remains small or at least fairly constant. If the type of objects is also growing or changing just like the operations, then this design pattern won’t help as much. This pattern is optimized to help you support many different types of operations and allow new operations to be added without needing to change a bunch of code in the object classes.
Here’s how it’s implemented with an example consisting of two object types and two operation types:
You define a single method in the object interface called accept which takes a single parameter of a new interface called visitor.
Then you define your concrete object types.
concrete class: objectOne implements objectInterface
concrete class: objectTwo implements objectInterface
The visitor interface will have methods for each concrete object type.
And then you define concrete classes for each operation. These are the visitors.
concrete class: operationOne implements visitor
concrete class: operationTwo implements visitor
Then when some code wants to perform operationOne on each of the objects in a collection, it iterates through each instance and calls accept and passes operationOne as the visitor. When a particular object instance, say objectTwo, has its accept method called, it calls back to the visitor through the object specific method and passes itself as the parameter. This ends up in operationOne’s implementation of the visitObjectTwo method.
This pattern is able to match both the specific operation type with the specific object type. This is done through a double-dispatch where the first dispatch happens when the object’s virtual method is invoked and the second dispatch occurs when the visitor’s virtual method is invoked.
The really nice thing about this pattern though is that because each object just calls back to the visitor, there’s no real code in the object. Anytime a new operation is needed, you just need to add a new concrete implementation of the visitor interface and all the existing object code can remain unchanged.