© Vaskaran Sarcar 2019
Vaskaran SarcarJava Design Patternshttps://doi.org/10.1007/978-1-4842-4078-6_13

13. Visitor Pattern

Vaskaran Sarcar1 
(1)
Bangalore, Karnataka, India
 

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

Figure 13-1 shows the class diagram.
../images/395506_2_En_13_Chapter/395506_2_En_13_Fig1_HTML.jpg
Figure 13-1

Class diagram

Package Explorer View

Figure 13-2 shows the high-level structure of the program.
../images/395506_2_En_13_Chapter/395506_2_En_13_Fig2_HTML.jpg
Figure 13-2

Package Explorer view

Implementation

Here’s the implementation.
package jdp2e.visitor.demo;
interface OriginalInterface
{
    //The following method has a Visitor argument.
    void acceptVisitor(Visitor visitor);
}
class MyClass implements OriginalInterface
{
    //Here "myInt" is final.So, once initialized, it should not be changed.
    private final int myInt;
    public MyClass()
    {
        myInt=5;//Initial or default value
    }
    public int getMyInt()
    {
        return myInt;
    }
    @Override
    public void acceptVisitor(Visitor visitor)
    {
        visitor.visit(this);
    }
}
interface Visitor
{
    //The method to visit MyClass
    void visit(MyClass myClassObject);
}
class ConcreteVisitor implements Visitor
{
    @Override
    public void visit(MyClass myClassObject)
    {
        System.out.println("Current value of myInt="+ myClassObject.getMyInt());
        System.out.println("Visitor will make it double and display it.");
        System.out.println("Current value of myInt="+ 2*myClassObject.getMyInt());
        System.out.println("Exiting from Visitor.");
    }
}
public class VisitorPatternExample {
    public static void main(String[] args) {
        System.out.println("***Visitor Pattern Demo*** ");
        Visitor visitor = new ConcreteVisitor();
        MyClass myClass = new MyClass();
        myClass.acceptVisitor(visitor);
    }
}

Output

Here’s the output.
***Visitor Pattern Demo***
Current value of myInt=5
Visitor will make it double and display it.
Current value of myInt=10
Exiting from Visitor.

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

Let’s revisit the example of our 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 teachers (or professors/lecturers). Each HOD reports to the principal of the college. Figure 13-3 shows the tree structure that I discussed in that chapter.
../images/395506_2_En_13_Chapter/395506_2_En_13_Fig3_HTML.jpg
Figure 13-3

Tree structure of a composite design 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

Figure 13-4 shows step 1.
../images/395506_2_En_13_Chapter/395506_2_En_13_Fig4_HTML.jpg
Figure 13-4

Step 1

Step 2

Figure 13-5 shows step 2.
../images/395506_2_En_13_Chapter/395506_2_En_13_Fig5_HTML.jpg
Figure 13-5

Step 2

Step 3

Figure 13-6 shows step 3.
../images/395506_2_En_13_Chapter/395506_2_En_13_Fig6_HTML.jpg
Figure 13-6

Step 3

Step 4

Figure 13-7 shows step 4.
../images/395506_2_En_13_Chapter/395506_2_En_13_Fig7_HTML.jpg
Figure 13-7

Step 4

Step 5

Figure 13-8 shows step 5.
../images/395506_2_En_13_Chapter/395506_2_En_13_Fig8_HTML.jpg
Figure 13-8

Step 5

And so on…

In the following implementation, there are code blocks like the following.
@Override
public void acceptVisitor(Visitor visitor)
{
    visitor.visitTheElement(this);
}
From this structure, you can see two important things.
  • 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

Figure 13-9 shows the modified class diagram.
../images/395506_2_En_13_Chapter/395506_2_En_13_Fig9_HTML.jpg
Figure 13-9

Modified class diagram

Modified Package Explorer View

Figure 13-10 shows the high-level structure of the modified program.
../images/395506_2_En_13_Chapter/395506_2_En_13_Fig10_HTML.jpg
Figure 13-10

Modified Package Explorer view

Modified Implementation

Here’s the modified implementation.
package jdp2e.visitor.modified.demo;
import java.util.ArrayList;
import java.util.List;
interface Employee
{
    void printStructures();
    //The following method has a Visitor argument.
    void acceptVisitor(Visitor visitor);
}
//Employees who have Subordinates
class CompositeEmployee implements Employee
{
    private String name;
    private String dept;
    //New field for this example.
    //It is tagged with "final", so visitor cannot modify it.
    private final int  yearsOfExperience;
    //The container for child objects
    private List<Employee> controls;
    // Constructor
    public CompositeEmployee(String name,String dept, int experience)
    {
        this.name = name;
        this.dept = dept;
        this.yearsOfExperience = experience;
        controls = new ArrayList<Employee>();
    }
    public void addEmployee(Employee e)
    {
        controls.add(e);
    }
    public void removeEmployee(Employee e)
    {
        controls.remove(e);
    }
    // Gets the name
    public String getName()
    {
        return name;
    }
    // Gets the department name
    public String getDept()
    {
        return dept;
    }
    // Gets the yrs. of experience
    public int getExperience()
    {
        return yearsOfExperience;
    }
    public List<Employee> getControls()
    {
        return this.controls;
    }
    @Override
    public void printStructures()
    {
        System.out.println(" " + getName() + " works in  " + getDept() + " Experience :" + getExperience() + " years");
        for(Employee e: controls)
        {
            e.printStructures();
        }
    }
    @Override
    public void acceptVisitor(Visitor visitor)
    {
        visitor.visitTheElement(this);
    }
}
class SimpleEmployee implements Employee
{
    private String name;
    private String dept;
    //New field for this example
    private int yearsOfExperience;
    //Constructor
    public SimpleEmployee(String name, String dept, int experience)
    {
        this.name = name;
        this.dept = dept;
        this.yearsOfExperience = experience;
    }
    // Gets the name
    public String getName()
    {
        return name;
    }
    // Gets the department name
    public String getDept()
    {
        return this.dept;
    }
    // Gets the yrs. of experience
    public int getExperience()
    {
        return yearsOfExperience;
    }
    @Override
    public void printStructures()
    {
        System.out.println(" " + getName() + " works in  " + getDept() + " Experience :" + getExperience() + " years");
    }
    @Override
    public void acceptVisitor(Visitor visitor)
    {
        visitor.visitTheElement(this);
    }
}
interface Visitor
{
    void visitTheElement(CompositeEmployee employees);
    void visitTheElement(SimpleEmployee employee);
}
class ConcreteVisitor implements Visitor
{
    @Override
    public void visitTheElement(CompositeEmployee employee)
    {
        //We'll promote them if experience is greater than 15 years
        boolean eligibleForPromotion = employee.getExperience() > 15 ? true : false;
        System.out.println(" " + employee.getName() + " from  " + employee.getDept() + " is eligible for promotion? " + eligibleForPromotion);
    }
    @Override
    public void visitTheElement(SimpleEmployee employee)
    {
        //We'll promote them if experience is greater than 12 years
        boolean eligibleForPromotion = employee.getExperience() > 12 ? true : false;
        System.out.println(" " + employee.getName() + " from  " + employee.getDept() + " is eligible for promotion? " + eligibleForPromotion);
    }
}
public class ModifiedVisitorPatternExample {
    public static void main(String[] args) {
        System.out.println("***Visitor Pattern combined with Composite Pattern Demo*** ");
        /*2 teachers other than HOD works in
         Mathematics department*/
        SimpleEmployee mathTeacher1 = new SimpleEmployee("Math Teacher-1","Maths",13);
        SimpleEmployee mathTeacher2 = new SimpleEmployee("Math Teacher-2","Maths",6);
        /* 3 teachers other than HOD works in
          Computer Sc. department*/
        SimpleEmployee cseTeacher1 = new SimpleEmployee("CSE Teacher-1","Computer Sc.",10);
        SimpleEmployee cseTeacher2 = new SimpleEmployee("CSE Teacher-2", "Computer Sc.",13);
        SimpleEmployee cseTeacher3 = new SimpleEmployee("CSE Teacher-3", "Computer Sc.",7);
        //The College has 2 Head of Departments-One from Mathematics, One from Computer Sc.
        CompositeEmployee hodMaths = new CompositeEmployee("Mrs.S.Das(HOD-Maths)","Maths",14);
        CompositeEmployee hodCompSc = new CompositeEmployee("Mr. V.Sarcar(HOD-CSE)", "Computer Sc.",16);
        //Principal of the college
        CompositeEmployee principal = new CompositeEmployee("Dr.S.Som(Principal)","Planning-Supervising-Managing",20);
        //Teachers of Mathematics directly reports to HOD-Maths
        hodMaths.addEmployee(mathTeacher1);
        hodMaths.addEmployee(mathTeacher2);
        //Teachers of Computer Sc. directly reports to HOD-CSE
        hodCompSc.addEmployee(cseTeacher1);
        hodCompSc.addEmployee(cseTeacher2);
        hodCompSc.addEmployee(cseTeacher3);
        /*Principal is on top of college.HOD -Maths and Comp. Sc directly reports to him */
        principal.addEmployee(hodMaths);
        principal.addEmployee(hodCompSc);
        System.out.println(" Testing the overall structure");
        //Prints the complete structure
        principal.printStructures();
        System.out.println(" ***Visitor starts visiting our composite structure*** ");
        System.out.println("---The minimum criteria for promotion is as follows ---");
        System.out.println("--Junior Teachers-12 years and Senior teachers-15 years.-- ");
        Visitor myVisitor = new ConcreteVisitor();
        /*
         * At first, we are building a container for employees who will be considered for promotion .
         Principal is holding the highest position.So, he is not considered for promotion.
         */
        List<Employee> employeeContainer= new ArrayList<Employee>();
        //For employees who directly reports to Principal
        for (Employee e : principal.getControls())
        {
            employeeContainer.add(e);
        }
        //For employees who directly reports to HOD-Maths
        for (Employee e : hodMaths.getControls())
        {
            employeeContainer.add(e);
        }
        //For employees who directly reports to HOD-Comp.Sc
        for (Employee e : hodCompSc.getControls())
        {
            employeeContainer.add(e);
        }
        //Now visitor can traverse through the container.
        for (Employee e :employeeContainer)
        {
            e.acceptVisitor(myVisitor);
        }
    }
}

Modified Output

Here’s the modified output.
***Visitor Pattern combined with Composite Pattern Demo***
 Testing the overall structure
 Dr.S.Som(Principal) works in  Planning-Supervising-Managing Experience :20 years
   Mrs.S.Das(HOD-Maths) works in  Maths Experience :14 years
     Math Teacher-1 works in  Maths Experience :13 years
     Math Teacher-2 works in  Maths Experience :6 years
   Mr. V.Sarcar(HOD-CSE) works in  Computer Sc. Experience :16 years
     CSE Teacher-1 works in  Computer Sc. Experience :10 years
     CSE Teacher-2 works in  Computer Sc. Experience :13 years
     CSE Teacher-3 works in  Computer Sc. Experience :7 years
***Visitor starts visiting our composite structure***
---The minimum criteria for promotion is as follows ---
--Junior Teachers-12 years and Senior teachers-15 years.--
   Mrs.S.Das(HOD-Maths) from  Maths is eligible for promotion? false
  Mr. V.Sarcar(HOD-CSE) from  Computer Sc. is eligible for promotion? true
   Math Teacher-1 from  Maths is eligible for promotion? true
   Math Teacher-2 from  Maths is eligible for promotion? false
  CSE Teacher-1 from  Computer Sc. is eligible for promotion? false
  CSE Teacher-2 from  Computer Sc. is eligible for promotion? true
  CSE Teacher-3 from  Computer Sc. is eligible for promotion? false

Q&A Session

  1. 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. 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. 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. 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. 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. 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. 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. 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.

     
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.173.242