Visitor is a behavioral design pattern that lets you define a new operation without changing the classes of the objects on which it operates.
Your team develops an app that works with geographic information structured as a graph. Nodes of the graph represent not only cities and towns but also many other types of locations, such as industries, sightseeings, etc. Nodes connected with each other if there is a road between them. Under the hood, each node is an object, and each node type represented by its own class.
You have got a task to make it possible to export the graph into XML. The job looked easy at first. You would need to add an export method for each node type and then go over the graph, executing this method for each node. The solution was not only simple but also elegant, since you leveraged the polymorphism to avoid coupling to concrete node classes.
But unfortunately, the system architect refused to allow you to alter existing node classes. The code was already in production and nobody wanted to risk breaking it.
In addition, he questioned whether the XML export makes sense in node classes in first place. The primary job of these classes is to work with geodata. The export behavior would look alien there.
There was another reason for the refusal. In close future, someone from the marketing department may ask you to change the export to a different format or add some other weird feature. That would force you to change that precious code again.
The Visitor pattern suggests placing a new behavior into a separate class, instead of integrating it into existing classes. Objects related to the behavior, will not be calling it by themselves. They will be passed as arguments to methods of the visitor object instead.
The behavior's code may be a bit different for different types of objects. Hence, the visitor class must have a set of behavioral methods with different parameter types:
But how exactly would we call these methods when dealing with a whole graph? The methods have different signatures, which does not allow us to use polymorphism. To pick a proper visitor's method able to process a given object, we would need to check its class. Does not that sound as a nightmare?
The method overloading will not help either, given that our programming language supports it at all (like Java or C#). That is when you give all methods the same name, even if they support a different set of parameters. But since the exact class of a given node is not known in advance, the overloading will not be able to determine the correct method to execute. It will default to a visitor's method that expects an object of a base
Node class as an argument.
But the Visitor pattern has a solution for that problem as well. It uses Double Displatch techniques to keep the polymorphism around. Instead of trying to pick a proper visitor's method ourselves, how about we delegate this to the objects we are passing to the visitor? Those objects are aware of their own class, which will let them pick a proper method less awkwardly.
I confess. We had to change the node classes after all. But at least, the change is trivial and let us add further behaviors without altering any more existing code.
To make it truly happen, we must go further and extract a common interface for all future visitors. Now, if you need to introduce a new behavior to the program, all you have to do is to implement a new visitor class. And all existing node classes will be able to work with it just fine.
Imagine a novice insurance agent, eager to get new customers. He randomly visits all the houses in the neighborhood, offering his services. But for each of the "types" of houses he visits, he has a special offer.
- To a residence, he sells medical insurance.
- To a bank, he sells theft insurance.
- To a business, he sells fire and floods insurance.
Visitor declares the common interface for all types of visitors. It declares a set of visiting methods that accept various Context Components as parameters. The methods may have the same names in languages that support overloading, but their parameter types must be different.
Concrete Visitors implements all the operations described in the common Visitor interface. Each concrete visitor represents a single behavior.
Component declares a method for accepting Visitors. The method should accept visitors as arguments using the common Visitor interface.
Concrete Component implements the acceptance method. The purpose of this method is to redirect the call to a proper visitor's method corresponding to the current component class.
Client represents a collection or some other complex object (for example, a Composite tree). Clients are usually not aware of the concrete classes of their components.
In this example, the Visitor pattern adds XML export support to a hierarchy of geometric shapes.
If you wonder why do we need the
accept method in this example, then read our article Visitor and Double Dispatch, which addresses that question in detail.
When you need to perform an operation on all elements of a complex object structure (for example, a tree), and all the elements are heterogeneous.
The Visitor pattern allows you to execute an operation over a set of objects with different classes.
When you need to be able to run several unrelated behaviors over a complex object structure, but you do not want to "clog" the structure's classes with the code of these behaviors.
The Visitor pattern allows you to extract and unify related behaviors from a bunch of classes that make an object structure, into a single visitor class. Such transformation will allow reusing these classes in various apps without carrying over nonrelevant behaviors
When a new behavior makes sense only for some classes from the existing hierarchy.
The Visitor pattern allows you to craft a special visitor class implements behavior for some objects, but not for others.
How to Implement
Create the Visitor interface and declare a "visiting" method for each concrete component class that exists in the program.
Add an abstract acceptance method to the base class of the component hierarchy.
Implement the acceptance methods in all concrete components. They must redirect calls to a particular visitor's method that has a parameter of the same class as the current component.
The component hierarchy should only be aware of the Visitor interface. On the other hand, visitors will be coupled to all concrete components.
For each new behavior, create a new Concrete Visitor class and implement all of the visiting methods.
The client will create visitor objects, and pass them into components via acceptance methods.
Pros and Cons
- Simplifies adding new operations over a complex object structure.
- Moves related behaviors into a single class.
- A visitor can accumulate state over the course of working with an object structure.
- The pattern is not justified if a hierarchy of components often changes.
- Violates encapsulation of components.
Relations with Other Patterns
- Read why Visitors can not be simply replaced with method overloading in our article Visitor and Double Dispatch.