21. Visitor Mode

1. Overview

Definition: Encapsulates operations that act on elements in a data structure to define new operations on those elements without changing the data structure.

2. Structure

Visitor mode contains the following main roles:

The Abstract Visitor role: defines the behavior of accessing each Element, and its parameters are the accessible elements. The number of methods is theoretically the same as the number of Element classes (the number of implementation classes of Elements). It is not difficult to see that the Visitor pattern requires that the number of Element classes cannot be changed.

ConcreteVisitor role: Gives the specific behavior that occurs when you access each element class.

Abstract Element Role: Defines a method of accept ing visitors, meaning that each element is accessible to visitors.

ConcreteElement role: Provides a concrete implementation of the method to be accessed, which typically uses the method provided by the visitor to access the element class.

Object Structure role: The object structure mentioned in the definition, which is an abstract representation, can be understood as a class with container or composite object properties that contains a set of elements and can be iterated over for visitors to access.

3. Case Realization

Feeding pets: There are a lot of people who have pets now. Let's take this as an example. Pets are also divided into dogs, cats and so on. If you want to feed your pets, the owner can feed them, and others can feed them.

Visitor Role: People feeding pets;

Specific visitor roles: owner, others;

Abstract Element Role: Animal Abstract Class;

Specific element roles: pet dog, pet cat;

Structural Object Role: Host.

The class diagram is as follows:

The code is as follows:

Create Abstract visitor interfaces

public interface Person {
    void feed(Cat cat);

    void feed(Dog dog);
}

Creating different specific visitor roles (host and others) requires implementing the Person interface

public class Owner implements Person {

    @Override
    public void feed(Cat cat) {
        System.out.println("Owner feeds cat");
    }

    @Override
    public void feed(Dog dog) {
        System.out.println("Master feeds dog");
    }
}

public class Someone implements Person {
    @Override
    public void feed(Cat cat) {
        System.out.println("Others feed cats");
    }

    @Override
    public void feed(Dog dog) {
        System.out.println("Others feed dogs");
    }
}

Define Abstract nodes--pets

public interface Animal {
    void accept(Person person);
}

Define specific nodes (elements) that implement the Animal interface

public class Dog implements Animal {

    @Override
    public void accept(Person person) {
        person.feed(this);
        System.out.println("Good to eat, Wangwang!!!");
    }
}

public class Cat implements Animal {

    @Override
    public void accept(Person person) {
        person.feed(this);
        System.out.println("Eat well, miao!!!");
    }
}

Define the object structure, in this case the owner's home

public class Home {
    private List<Animal> nodeList = new ArrayList<Animal>();

    public void action(Person person) {
        for (Animal node : nodeList) {
            node.accept(person);
        }
    }

    //Add Action
    public void add(Animal animal) {
        nodeList.add(animal);
    }
}

Test Class

public class Client {
    public static void main(String[] args) {
        Home home = new Home();
        home.add(new Dog());
        home.add(new Cat());

        Owner owner = new Owner();
        home.action(owner);

        Someone someone = new Someone();
        home.action(someone);
    }
}

4. Advantages and disadvantages

Strengths:

Extensibility to add new functionality to elements in an object structure without modifying elements in the object structure.

Improved reuse by allowing visitors to define functionality common to the entire object structure.

Separate unrelated behaviors, separate unrelated behaviors by visitors, and encapsulate related behaviors together to form a single visitor so that each visitor has a single function.

Disadvantages:

It is difficult to change the object structure. Every new element class is added to the visitor mode, specific actions are added to each specific visitor class, which violates the Open and Close Principle.

In violation of the principle of dependency inversion, the visitor pattern relies on specific classes instead of on abstract classes.

5. Use scenarios

Programs that have relatively stable object structures but frequently variable operation algorithms.

Objects in an object structure need to provide a variety of different and unrelated operations, and to avoid having changes in these operations affect the structure of the object.

6. Extension

Visitor mode uses a dual-allocation technology.

6.1 Assignment

When a variable is declared, the type is called the static type of the variable, and some people call the static type the obvious type, while the real type of the object it references is also called the actual type of the variable. For example, Map map = new HashMap ()The static type of the map variable is Map, and the actual type is HashMap. The choice of method based on the type of object is Dispatch, which is divided into two types, static and dynamic.

Static Dispatch occurs during compilation, and dispatches occur based on static type information. Static dispatch is not new to us, and method overload is static dispatch.

Dynamic Dispatch occurs during runtime and dynamic dispatch dynamically replaces a method. Java supports dynamic dispatch through method rewriting.

6.2 Dynamic Assignment

Dynamic assignment is supported through method overrides.

public class Animal {
    public void execute() {
        System.out.println("Animal");
    }
}

public class Dog extends Animal {
    @Override
    public void execute() {
        System.out.println("dog");
    }
}

public class Cat extends Animal {
     @Override
    public void execute() {
        System.out.println("cat");
    }
}

public class Client {
   	public static void main(String[] args) {
        Animal a = new Dog();
        a.execute();
        
        Animal a1 = new Cat();
        a1.execute();
    }
}

The results of the above code should be directly stated, isn't that polymorphic? Running executes methods in subclasses.

The Java compiler does not always know which code will be executed at compile time, because the compiler only knows the static type of the object, not the real type of the object; method calls are based on the real type of the object, not the static type.

6.3 Static Assignment

Static assignment is supported through method overload.

public class Animal {
}

public class Dog extends Animal {
}

public class Cat extends Animal {
}

public class Execute {
    public void execute(Animal a) {
        System.out.println("Animal");
    }

    public void execute(Dog d) {
        System.out.println("dog");
    }

    public void execute(Cat c) {
        System.out.println("cat");
    }
}

public class Client {
    public static void main(String[] args) {
        Animal a = new Animal();
        Animal a1 = new Dog();
        Animal a2 = new Cat();

        Execute exe = new Execute();
        exe.execute(a);
        exe.execute(a1);
        exe.execute(a2);
    }
}

Run result:

        

 

This may have come as a surprise to some people. Why?

The assignment of overloaded methods is based on static types, which are completed at compile time.

6.4 Double Assignment

The so-called double-dispatch technique is to choose a method based not only on the runtime of the receiver but also on the runtime of the parameter.

        

public class Animal {
    public void accept(Execute exe) {
        exe.execute(this);
    }
}

public class Dog extends Animal {
    public void accept(Execute exe) {
        exe.execute(this);
    }
}

public class Cat extends Animal {
    public void accept(Execute exe) {
        exe.execute(this);
    }
}

public class Execute {
    public void execute(Animal a) {
        System.out.println("animal");
    }

    public void execute(Dog d) {
        System.out.println("dog");
    }

    public void execute(Cat c) {
        System.out.println("cat");
    }
}

public class Client {
    public static void main(String[] args) {
        Animal a = new Animal();
        Animal d = new Dog();
        Animal c = new Cat();

        Execute exe = new Execute();
        a.accept(exe);
        d.accept(exe);
        c.accept(exe);
    }
}

In the code above, the client passes the Execute object as a parameter to a method called by a variable of type Animal, where the first assignment is done, where the method override is done, so the dynamic assignment, which is to execute the method in the actual type, also passes in its own this as a parameter, completes the second assignment, where the Execute classThere are multiple overloaded methods, and what is passed is this, which is the actual type of object.

At this point, we already know what's going on with double assignment, but what's the effect? It's a dynamic binding of methods that we can modify.

The results are as follows:

 

Dual assignment implements dynamic binding by adding coverage in the inheritance system to the front of overload method delegation. Since coverage is dynamic, overload is dynamic.

        

Tags: Java Algorithm

Posted on Thu, 30 Sep 2021 13:15:06 -0400 by bolter