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
In this pattern, you separate an algorithm from an object structure. So, you can add new operations on objects without modifying their existing architecture. This pattern supports the open/close principle (which says the extension is allowed, but modification is disallowed for entities such as class, function, and so on).
You can experience the true power of this design pattern when you combine it with the Composite pattern, as shown in an implementation later in this chapter.
This inheritance hierarchy is easy to understand. Now let’s look at an imaginary conversation between you and your customer.
Customer: I want you to create a design in which each concrete class has a method to increment the number value.
You: That’s easy. I’ll introduce a common method in the Number class, and as a result, each of the concrete classes can get the method.
Customer: Wait. I want you to use a method that increments the number, but in each invocation of the method in the SmallNumber class, it should increment the number by 1, and for the BigNumber class, it should increment the number by 10.
You: That won’t be a problem. I can define an abstract method in the Number class, and in each of the derived classes, you can implement it differently.
Customer: That’s fine with me.
You can accept this customer request as a one-off, but if your client often asks for similar requests, will it be possible for you to introduce methods like this in each class, particularly when the overall code structure is very complex? Also, in a tree structure, if it is just a branch node, can you imagine the impact of these changes across other nodes?
This time you may understand the problem and may think of some way to handle your fickle-minded customers. The Visitor pattern can help you in a situation like this. You see such an implementation in demonstration 1.
Real-World Example
Think of a taxi-booking scenario. When the taxi arrives at your door, and you enter the vehicle, the taxi driver takes control of transportation. He can take you to your destination through a route that you are not familiar with, and in the worst case, it can alter the destination (which is generated due to improper use of the Visitor pattern).
Computer-World Example
This pattern is useful when public APIs need to support plug-in operations. Clients can then perform their intended operations on a class (with the visiting class) without modifying the source.
Implementation
Instead of using different names (VisitSmallNumbers(..), VisitBigNumbers(...)) for these methods, you could use the same method (for example, VisitNumbers(...)) by using method overloading. In the Q&A session, I discuss the reason for using different names in this example.
One interesting point to note is that I do not want to modify the original data. So, in the Number class, you see the getter methods only. It is because I assume that once you get the data from the concrete Number classes, you can use it differently, but you are not allowed to change the original data. (It’s a better practice, but it’s optional).
Class Diagram
Solution Explorer View
Demonstration 1
Output
Q&A Session
13.1 When should you consider implementing a Visitor design pattern?
You need to add new operations to a set of objects without changing their corresponding classes. It is the primary aim to implement a Visitor pattern. When the operations change very often, this approach can be your savior.
If you need to change the logic of various operations, you can simply do it through a visitor implementation.
13.2 Are there any drawbacks associated with this pattern?
I mentioned earlier that encapsulation is not its key concern. So, you can break the power of encapsulation using visitors.
If you need to frequently add new concrete classes to existing architecture, the visitor hierarchy becomes difficult to maintain. For example, suppose that you want to add another concrete class in the original hierarchy. In this case, you need to modify the visitor class hierarchy accordingly.
13.3 Why are you saying that a visitor class can violate the encapsulation ?
Notice that inside the Accept method, you can pass a “particular visitor object,” which in turn can call the appropriate method across the classes. Both SmallNumber and BigNumber class expose themselves through this method, and here encapsulation is compromised.
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 with that information. (Though in demonstration 1, I do not allow this modification). So, when you provide this kind of support, you violate the core aim of encapsulation.
13.4 Why this pattern compromises with the encapsulation?
Here you perform some operations on a set of objects that can be heterogeneous also. But your constraint is that you cannot change their corresponding classes. So, your visitor needs a way to access the members of these objects. To fulfill this requirement, you are exposing the information to the visitor.
Is this correct?
Nice catch. Yes, you can do that, but I wanted to draw your attention to the fact that these methods are doing different jobs (one is incrementing the int by 1 and the other is incrementing it by 10). By using different names, I tried to distinguish them inside the Number class hierarchy when you go through the code.
In the book Java Design Patterns (Apress, 2018), I used the approach that you mentioned. You can simply remember that these interface methods should target specific classes like SmallNumber or BigNumber only.
In demonstration 2, in which I combine the Visitor pattern with the Composite pattern, the overloaded methods are used.
13.6 Suppose that in demonstration 1, I added another concrete subclass of Number called UndefinedNumber . How should I proceed? Should I use another specific method in the visitor interface?
And later, you need to implement this new method in the concrete visitor class.
13.7 Suppose, I need to support new operations in the existing architecture. How should I proceed with a Visitor pattern?
You can download the full code for this modified example from the Apress website. I merged this in the namespace called VisitorPatternDemo2.
13.8 I see that you are initializing numberList with objects for SmallNumber and BigNumber. Is it mandatory to create such a structure?
No. I make a container that helps the client to visit smoothly in one shot. In a different variation, you could see that you initialize an empty list first and add (or remove) elements to this inside client code before you traverse the list.
To understand the previous line, you can refer to demonstration 2, where I made the container class inside the client code only.
Using Visitor Pattern and Composite Pattern Together
In demonstration 1, you saw an example of the Visitor design pattern, and in the Q&A session, you went through an extended version of it. Now I’ll show you another implementation, but this time, I combine it with the Composite pattern.
Let’s consider the example of the Composite design pattern from Chapter 11. In that example, there is a college with two different departments. Each of these departments has one head of department (HOD) and multiple professors/lecturers. All HODs report to the principal of the college.
Now suppose that the principal of the college wants to promote some of the employees. Let’s say that teaching experience is the only criteria for promotion, but the criteria varies between senior teachers (branch nodes) and junior teachers (leaf nodes) as follows: for a junior teacher, the minimum criteria for promotion is 12 years, and for senior teachers, it is 15 years.
The visitor is collecting the data one piece at a time from the original college structure without making any modifications to it. Once the collection process is over, the visitor analyzes the data to display the intended results. To understand this visually, you can follow the arrows in Figures 13-4 through 13-8. The principal is at the top of the organization, so you can assume that he receives no promotion.
Step 1
Step 2
Step 3
Step 4
Step 5
And so on...
I followed a similar design in demonstration 1, and the code example is built on top of the only demonstration in Chapter 11. For brevity, I do not include the class diagram and Solution Explorer view for this example. So, go directly through the following implementation.