Visitor mode of design mode

Time:2022-6-10

Visitor mode belongs to behavior mode; It refers to separating the operations that act on each element in a data structure and encapsulating them into independent classes, so that they can add new operations that act on these elements without changing the data structure, and provide multiple access methods for each element in the data structure. It separates the operation of data from the data structure. It is the most complex pattern among the behavior patterns.

The purpose of visitor pattern is to encapsulate some operations imposed on certain data structure elements. Once these operations need to be modified, the data structure that accepts this operation can remain unchanged.

Double Dispatch

Each node of the data structure can accept the call of an accessor. This node passes in the node object to the accessor object, and the accessor object in turn performs the operation of the node object. This process is called “double dispatch”. The node calls the visitor and passes it in. The visitor executes an algorithm on this node.
Double dispatch means that the operation imposed on the node is based on the data type of the visitor and the node itself, not just one of them.

Single dispatch and multi dispatch

The receiver of a method and the parameters of a method are collectively referred to as method quantities.According to the number of parcels on which dispatch is based (the receiver is a parcel and the parameter is a parcel), dispatch can be divided into single dispatch and multiple dispatch. Single dispatch means that the call target (i.e. which method should be called) can be known according to one parcel. Multiple dispatch requires multiple parcels to determine the call target.

The UML class diagram of the visitor pattern is as follows:

As can be seen from the above figure, the visitor pattern involves six roles: Abstract visitor role, concrete visitor role, abstract node role, concrete node role, structure object role and client role:

  • Abstract visitor role: it declares one or more access operations and forms the interfaces that all concrete element roles must implement.
  • Concretevisitor role: implements the interface declared by the abstract visitor role, that is, each access operation declared by the abstract visitor.
  • Abstract node role: declare an accept operation and accept an accessor object as a parameter.
  • Node role: implements the acceptance operation specified by the abstract element.
  • Objectstructure role: it has the following responsibilities and can traverse all elements in the structure; If necessary, provide a high-level interface so that the visitor object can access each element; If necessary, it can be designed as a composite object or an aggregate, such as a list or set.

As can be seen from the above figure, the abstract visitor role prepares an access operation for each specific node. Since there are two nodes, there are two corresponding access operations.

Voting examples

We have all seen a lot of singing talent shows. When a performer finishes performing, the following judges need to evaluate his performance (promotion, elimination, to be determined) to decide whether he will be promoted to the next round. We will take this as an example to explain the visitor design mode.

Example UML class diagram:

Abstract node role:

package com.charon.visitor;

/**
 * @className: Person
 * @description:
 * @author: charon
 * @create: 2022-03-27 17:18
 */
public abstract class Person {

    abstract void accept(Action action);
}

Specific node roles:

package com.charon.visitor;

/**
 * @className: Man
 * @description:
 * @author: charon
 * @create: 2022-03-27 17:19
 */
public class Man extends Person {

    private String name;

    public Man(String name) {
        this.name = name;
    }

    /**
     * Gets the value of name
     *
     * @return the value of name
     */
    public String getName() {
        return name;
    }

    @Override
    void accept(Action action) {
        action.getManResult(this);
    }
}

package com.charon.visitor;

/**
 * @className: Woman
 * @description:
 * @author: charon
 * @create: 2022-03-27 17:19
 */
public class Woman extends Person{

    private String name;

    public Woman(String name) {
        this.name = name;
    }

    /**
     * Gets the value of name
     *
     * @return the value of name
     */
    public String getName() {
        return name;
    }

    @Override
    void accept(Action action) {
        action.getWomanResult(this);
    }
}

Abstract visitor role:

package com.charon.visitor;

/**
 * @className: Action
 * @description:
 * @author: charon
 * @create: 2022-03-27 17:19
 */
public abstract class Action {

    /**
     *Get a man's opinion
     * @param man
     */
    abstract void getManResult(Man man);

    /**
     *Get women's evaluation results
     * @param woman
     */
    abstract void getWomanResult(Woman woman);
}

Specific visitor roles:

package com.charon.visitor;

/**
 * @className: FailAction
 * @description:
 * @author: charon
 * @create: 2022-03-27 17:21
 */
public class FailAction extends Action{
    @Override
    void getManResult(Man man) {
        System. out. Println (man.getname() + "the evaluation given is that the performer's performance is eliminated...");
    }

    @Override
    void getWomanResult(Woman woman) {
        System. out. Println (woman.getname() + "the evaluation given is that the performer's performance is eliminated...");
    }
}

package com.charon.visitor;

/**
 * @className: SuccessAction
 * @description:
 * @author: charon
 * @create: 2022-03-27 17:22
 */
public class SuccessAction extends Action{
    @Override
    void getManResult(Man man) {
        System. out. Println (man.getname() + "the evaluation given is that the performer's performance is promoted...");
    }

    @Override
    void getWomanResult(Woman woman) {
        System. out. Println (woman.getname() + "the evaluation given is that the performer's performance is promoted...");
    }
}

Structure object roles:

package com.charon.visitor;

import java.util.ArrayList;
import java.util.List;

/**
 * @className: ObjectStructure
 * @description:
 * @author: charon
 * @create: 2022-03-27 17:25
 */
public class ObjectStructure {

    /**
     *Collection, used to store the evaluators of the performance
     */
    private List persons = new ArrayList<>();

    public void add(Person person){
        persons.add(person);
    }

    public void remove(Person person){
        persons.remove(person);
    }

    /**
     *Display evaluation results
     * @param action
     */
    public void display(Action action){
        for (Person person : persons) {
            person.accept(action);
        }
    }
}

Test:

package com.charon.visitor;

/**
 * @className: Client
 * @description:
 * @author: charon
 * @create: 2022-03-27 17:29
 */
public class Client {

    public static void main(String[] args) {
        ObjectStructure structure = new ObjectStructure();

        structure. Add (new man);
        structure. Add (new man);
        structure. Add (New Woman ("judge 1");
        structure. Add (New Woman ("judge 2");

        structure.display(new SuccessAction());
    }
}

Print:
    The evaluation given by male judge 1 is that the performer performs and is promoted....
    The evaluation given by male judge 2 is that the performer is qualified for performing....
    The evaluation given by female judge 1 is that the performer is qualified for performing....
    The evaluation given by female judge 2 is that the performer is qualified for performing....

As shown above, the code of the visitor mode is completed. If you need to add a pending operation type, you only need to add a waitaction:

package com.charon.visitor;

/**
 * @className: WaitAction
 * @description:
 * @author: charon
 * @create: 2022-03-27 17:39
 */
public class WaitAction extends Action{
    @Override
    void getManResult(Man man) {
        System. out. Println (man.getname() + "the evaluation given is that the performer's performance is pending...");
    }

    @Override
    void getWomanResult(Woman woman) {
        System. out. Println (woman.getname() + "the evaluation given is that the performer's performance is pending...");
    }
}

The visitor mode has the following advantages:

  1. Good scalability. It can add new functions to the elements in the object structure without modifying the elements in the object structure.
  2. Good reusability. Visitors can define the common functions of the whole object structure, so as to improve the reusability of the system.
  3. Good flexibility. Visitor mode decouples the data structure from the operations on the structure, so that the set of operations can evolve relatively freely without affecting the data structure of the system.
  4. Conform to the principle of single responsibility. Visitor mode encapsulates the relevant behaviors to form a visitor, which makes each visitor have a single function.

The disadvantages of visitor mode are as follows:

  1. Adding new element classes is difficult. In the visitor mode, each time a new element class is added, the corresponding specific operations must be added to each specific visitor class, which violates the “open close principle”.
  2. Destroy the package. Concrete elements in the visitor pattern publish details to visitors, which destroys the encapsulation of objects.
  3. It violates the dependency inversion principle. The visitor pattern relies on concrete classes rather than abstract classes.

Because of these shortcomings of visitor mode, many people oppose the use of visitor mode.

Application scenarios of visitor mode

When there is a type of data structure with a stable (fixed) number of types in the system, the visitor mode can be used to conveniently implement different operations on all data structures of this type without any side effects (dirty data). In short, the visitor pattern is used when multiple operations are performed on different types of data in the collection (the number of types is stable).

You can usually consider using the visitor pattern in the following cases.

  1. The object structure is relatively stable, but its operation algorithm often changes.
  2. Objects in the object structure need to provide a variety of different and unrelated operations, and the changes of these operations should not affect the object structure.
  3. The object structure contains many types of objects. You want to perform some operations on these objects that depend on their specific types.