fbpx

What is the visitor design pattern? The visitor behavioral pattern allows you to perform actions on a collection of different types where the actions depend on the types. It’s an alternative to using virtual methods and lets you add new actions without changing 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 the 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 pattern 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 the visitor pattern is 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.
interface: objectInterface
methods:
accept(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.
interface: visitor
methods:
visitObjectOne(objectOne)
visitObjectTwo(objectTwo)

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.

If you’d like to read the book that describes this pattern along with diagrams and sample code, then you can find Design Patterns at the Resources page. You can find all my favorite books and resources at this page to help you create better software designs.

Listen to the full episode for more or read the full transcript below.

Transcript

The basic description says that this pattern represents an operation that can be performed on a collection of objects and allow new operations to be defined without changing the objects.

There are all kinds of collections and episodes 39 through 44 describe just a few. And episode 66 describes the composite structural pattern which allows you to group objects together and treat them as a single object.

All of these collections have one thing in common though.

Everything in the collection must have something in common and that common part is what the collection refers to.

If we use the adventure game example and consider a backpack, the hero might have many different types of objects in the backpack, from apples to clothing to weapons. In order for the backpack to be able to reference them all, there needs to be something in common. Probably an interface called inventoryItem that each object implements. Then the backpack can have pointers to many inventoryItems.

This works great and you can even put methods in the common interface that all the objects should implement. Maybe anything in inventory has a weight. So it seems like a good idea to declare a method called weight. Now you can iterate through each item in inventory and add up the total weight by asking each item for its weight.

Here’s where you run into a problem though.

What if the hero’s backpack gets wet? The normal approach would be to add another method called makeWet to the common interface so that each item could implement it. What if an evil opponent casts a spell on the hero to rot any food? That would be another method. And the really bad thing about all this is that not every inventoryItem is food. It doesn’t make sense for a sword to now have to implement a rotFood method.

As the number of operations increases, the problem only gets worse. You could end up needing to define hundreds of operations in the common inventoryItem interface that each object type needs to implement.

But what other choice do you have? Because from outside the collection, there’s no way to tell an apple from a sword. Everything is this common interface.

Whenever you find yourself with a fixed number of object types or at least a set of object types that rarely changes and a dynamic and growing list of operations that needs to be performed on the objects, then this design pattern will save the day. And probably save the entire project.

The implementation is a little involved so I’ll explain it all right after this message from our sponsor.

( Message from Sponsor )

The secret to implementing the visitor design pattern involves something called double-dispatch.

I’ve already explained how virtual methods work in episode 25. You might want to listen to that episode first if you don’t know what virtual methods are and how to use them. You’ll probably won’t understand this explanation without that background knowledge.

A virtual method allows you to select which method will be called based on the actual type of the object that the method is called on. Don’t confuse this with method overloading which also allows you to select which method to call based on the method name and type of parameters used to call the method.

When you put both concepts of method overloading and method overriding together, you have the ability to select the actual method that will be called.

The method overloading will select the best method signature based on the parameter types and then the method overriding will perform a single dispatch to find the actual method based on the specific type of object that exists.

In other words, there could be some code that calls a rotFood method on an inventoryItem pointer which happens to point to a specific inventoryItem of type sword. This will end up executing the sword’s version of the rotFood method. Had the inventoryItem pointed to an apple instead, then the apple’s version of rotFood would have been called.

The dispatch is selected based on the type of object pointed to when the method call is made.

Now what if the rotFood method itself needs a parameter? Let’s say this parameter type is of type spell. There needs to be a spell class or more likely a spell interface that all spells can derive from.

A single-dispatch system like I just described doesn’t look at the actual type of the parameter spell. It only looks to see that some parameter of type spell was used to call the rotFood method and this is used to select between multiple different overloaded version of the rotFood method if applicable.

When the method finally resolves to some real code, the method in the code has no idea of what specific type of spell was used. We’re back to the old problem of working with a derived type through a base type. When all we know is the base type, we don’t know what the actual derived type is. Virtual methods can help resolve this somewhat like I just described but they only go so far.

What we need is a way to perform a double-dispatch. Something that not only selects the right method based on the type handling the method but also based on the actual type of a parameter.

Some programming languages such as LISP already support this feature. I haven’t used languages like this so don’t have direct experience to explain them yet. If your language does support double-dispatching, then you may not need this design pattern.

Assuming you do, here’s how to implement it.

We’re going to reverse the approach that I described in the beginning. Instead of declaring all the methods such as makeWet and rotFood in the inventoryItem interface, you only need to declare a single method called accept which takes as its only parameter a new interface called visitor.

If you previously were looking at hundreds of methods that could be performed on inventoryItems, you’re now down to just one. And it’ll stay at just one method called accept.

Now for the accept method to take an interface called visitor, that means we’ll need a new interface called visitor.

What methods will visitor need?

Is this where those hundreds of operations go? No. that would just shift things around without actually making them better. What goes in the visitor interface are methods for each type of inventoryItem. This will result in methods such as visitApple, visitCloak, and visitSword for example. You might be able to make these methods more general if needed. So maybe you can get away with visitFood, visitClothing, and visitWeapon. You’ll have to work with this to come up with the best structure.

The point is though that the various visit methods represent all the different types of items that can be visited or that can exist in the collection or in the composite. This is why I said this pattern is good if these types are fixed or rarely change.

All that’s left now is to create concrete types that implement the visitor pattern. This is where you add the hundreds of operations. So you’ll have a makeWetVisitor and a rotFoodVisitor class. Whenever you need to add a new operation, just create a new concrete class that implements the visitor interface. You no longer have to change any other code. This pattern is optimized to handle operations like this that are always being added or even removed.

Alright, so how does this result in a double-dispatch?

We’re almost done. You start out with a collection of various items in the backpack and they’re all of type inventoryItem which has the accept method. So you call accept and pass whatever specific operation type you need. Let’s say this is the rotFood operation. This will be passed as a simple visitor interface.

The first dispatch happens when the actual inventoryItem gets resolved to an apple. The apple’s version of accept gets called. And here’s the second dispatch. The apple code calls the visitor back through the visitApple or through the visitFood method whichever you decided to go with. Now we’re back in the visitor concrete implementation of rotFood and the visitFood method was just called. Everything matches and we can perform the operation and cause the food to go bad. Had this instead been a sword, then the sword’s accept method would have been called which would call back into the visitor with the visitWeapon method. The rotFood operation would know that it was a piece of metal being visited and would do nothing.

The end result is that we’re now able to first determine the specific type of inventory item and then determine the specific type of visitor. You now have the ability to add many more operations without changing any code in the objects that are being visited. Everything gets handled with the right visitor and with the right object.