This chapter covers the visitor pattern.
GoF Definition
Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
Concept
This pattern helps you add new operations on the objects without modifying the corresponding classes, especially when your operations change very often. Ideally, visitors define class-specific methods, which work with an object of that class to support new functionalities. Here you separate an algorithm from an object structure, and you add new operations using a new hierarchy. Therefore, this pattern can support the open/close principle (extension is allowed but modification is disallowed for entities like class, function, modules, etc.). The upcoming implementations will make the concept clearer to you.
Note
You can experience the true power of this design pattern when you combine it with the composite pattern, as shown in the modified implementation later in this chapter.
Real-World Example
Think of a taxi-booking scenario. When the tax arrives and you get into it, the taxi driver takes the control of the transportation. The taxi may take you to your destination through a new route that you are not familiar with. So, you can explore the new route with the help of the taxi driver. But you should use the visitor pattern carefully, otherwise, you may encounter some problem. (For example, consider a case when your taxi driver alters the destination unknowingly, and you face the trouble).
Computer-World Example
This pattern is very useful when public APIs need to support plugging operations. Clients can then perform their intended operations on a class (with the visiting class) without modifying the source.
Note
In Java, you may notice the use of this pattern when you use the abstract class org.dom4j.VisitorSupport, which extends Object and implements the org.dom4j.Visitor interface. Also, when you work with the javax.lang.model.element.Element interface or javax.lang.model.element.ElementVisitor<R,P> (where R is the return type of visitor’s method and P is the type of additional parameter to the visitor’s method), you may notice the use of visitor design pattern.
Illustration
Here our discussion will start with a simple example of the visitor design pattern. Let’s assume that you have an inheritance hierarchy where a MyClass concrete class implements the OriginalInterface interface. MyClass has an integer, myInt. When you create an instance of MyClass, it is initialized with a value, 5. Now suppose, you want to update this initialized value and display it. You can do it in two different ways: you can add a method inside MyClass to do your job or use a visitor pattern, which I am about to explain.
In the following implementation, I am multiplying the existing value by 2 and displaying this double value of myInt using the visitor design pattern. If I do not use this pattern, I need to add an operation (or method) inside MyClass, which does the same.
But there is a problem with the later approach. If you want to further update the logic (e.g., you want to triple myInt and display the value), you need to modify the operation in MyClass. One drawback with this approach is that if there are many classes involved, it will be tedious to implement this updated logic in all of them.
But in a visitor pattern, you can just update the visitor’s method. The advantage is that you do not need to change the original classes. This approach helps you when your operations change quite often.
So, let’s start with an example. Let’s assume that in this example, you want to double the initial integer value in MyClass and manipulate it, but your constraint is that you cannot change the original codes in the OriginalInterface hierarchy. So, you are using a visitor pattern in this case.
To achieve the goal, in the following example, I am separating the functionality implementations (i.e., algorithms) from the original class hierarchy.
Class Diagram
Package Explorer View
Implementation
Output
Modified Illustration
You have already seen a very simple example of the visitor design pattern. But you can exercise the true power of this design pattern when you combine it with the composite pattern (see Chapter 11). So, let’s examine a scenario where you need to combine both the composite pattern and the visitor pattern.
Key Characteristic of the Modified Example
Now suppose that the principal of the college wants to promote a few employees. Let’s consider that teaching experience is the only criteria to promote someone. Ideally, the criteria should vary among senior teachers and junior teachers. So, let’s assume that for a junior teacher, the minimum criteria for promotion is 12 years and for senior teachers, it is 15 years.
To accomplish this, you need to introduce a new field, yearsOfExperience. So, when a visitor gathers the necessary information from the college, it shows the eligible candidates for promotion.
The visitor is collecting the data from the original college structure without making any modifications to it, and once the collection process is over, it analyses the data to display the intended results. To understand this visually, you can follow the arrows in the upcoming figures. The principal is at the top of the organization, so you can assume that no promotion is required for that person.
Step 1
Step 2
Step 3
Step 4
Step 5
And so on…
Each time a visitor visits a particular object, the object invokes a method on the visitor, passing itself as an argument. The visitor has methods that are specific to a particular class.
Objects of the concrete employee classes (CompositeEmployee, SimpleEmployee) only implement the acceptVisitor(Visitor visitor) method. These objects know about the specific method of the visitor (which is passed as an argument here) that it should invoke.
So, let’s start.
Modified Class Diagram
Modified Package Explorer View
Modified Implementation
Modified Output
Q&A Session
- 1.
When should you consider implementing visitor design patterns?
You need to add new operations to a set of objects without changing their corresponding classes. It is one of the primary aims to implement a visitor pattern. When the operations change very often, this approach can be your savior. In this pattern, encapsulation is not the primary concern.
If you need to change the logic of various operations, you can simply do it through visitor implementation.
- 2.Are there any drawbacks associated with this pattern?
Encapsulation is not its key concern. So, you can break the power of encapsulation by using visitors.
If you need to frequently add new concrete classes to an existing architecture, the visitor hierarchy becomes difficult to maintain. For example, suppose you want to add another concrete class in the original hierarchy now. Then in this case, you need to modify visitor class hierarchy accordingly to fulfill the purpose.
- 3.
Why are you saying that a visitor class can violate the encapsulation?
In our illustration, I have tested a very simple visitor design pattern in which I show an updated integer value of myInt through the visitor class. Also, in many cases, you may see that the visitor needs to move around a composite structure to gather information from them, and then it can modify that information. So, when you provide this kind of support, you violate the core aim of encapsulation.
- 4.
Why does this pattern compromise the encapsulation?
Here you perform some operations on a set of objects that can be heterogeneous. But your constraint is that you cannot change their corresponding classes. So, your visitor needs a way to access the members of these objects. As a result, you need to expose the information to the visitor.
- 5.
In the visitor interfaces of the modified implementation, you are using the concept of method overloading ( i.e., method names are same). Is this mandatory?
No. In my book Design Patterns in C#, I used method names like VisitCompositeElement() and VisitLeafNode() in a similar context. Remember that these interface methods should target the specific classes, such as SimpleEmployee or CompositeEmployee.
- 6.
Suppose that in the modified implementation, I add a concrete subclass of Employee called UndefinedEmployee. How should I proceed? Should I have another specific method in the visitor interface?
Exactly. You need to define a new method that is specific to this new class. So, your interface may look like the following.interface Visitor{void visitTheElement(CompositeEmployee employees);void visitTheElement(SimpleEmployee employee);void visitTheElement(UndefinedEmployee employee);}And later you need to implement this new method in the concrete visitor class.
- 7.
Suppose that I need to support new operations in the existing architecture. How should I proceed with the visitor pattern?
For each new operation, create a new visitor subclass and implement the operation in it. Then, visit your existing structure the way that was shown in the preceding examples.
- 8.
In the client code, you made a container of employees first, and then it starts visiting. Is it mandatory to create such a structure?
No. It just helps clients to visit smoothly in one shot. If you do not create any such structure, you can always call it separately.