24, Visitor mode

Posted by asd on Wed, 03 Nov 2021 03:49:20 +0100

0. Ma Xian inspirational

Any restriction starts from your heart.

1. General

definition:

Encapsulates some operations that act on each element in a data structure. It can define new operations that act on these elements without changing the data structure.

2. Structure

The visitor pattern contains the following main roles:

  • Abstract Visitor role: defines the behavior of accessing each Element. Its parameter is the accessible Element. Theoretically, the number of methods is the same as the number of Element classes (the number of implementation classes of Element). It is not difficult to see that the Visitor mode requires that the number of Element classes cannot be changed.
  • Concrete visitor role: gives the specific behavior generated when accessing each element class.
  • Abstract Element role: defines a method to accept visitors. Its meaning means that every Element can be accessed by visitors.
  • Concrete element role: provides a concrete implementation that accepts the access method. Usually, this concrete implementation uses the method provided by the visitor to access the element class.
  • Object Structure role: the Object Structure mentioned in the definition. The Object Structure is an abstract expression. Specifically, it can be understood as a class with container or composite object characteristics. It will contain a group of elements and can replace these elements for visitors to access.

3. Case realization

Feeding pets

Now there are many pet owners. Let's take this as an example. Of course, pets are also divided into dogs, cats, etc. if you want to feed pets, the owner can feed them and others can feed them.

  • Visitor role: the person who feeds the pet
  • Specific visitor roles: host, others
  • Abstract element role: Animal abstract class
  • Specific element roles: pet dog, pet cat
  • Structure object role: master home

Class diagram is as follows:

The code is as follows:

Create an abstract visitor interface

public interface Person {
    void feed(Cat cat);

    void feed(Dog dog);
}

To create different specific visitor roles (host and others), you need to implement the Person interface

public class Owner implements Person {

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

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

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

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

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("Eat well, woof woof!!!");
    }
}

public class Cat implements Animal {

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

Define the object structure. In this case, it is the master'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 operation
    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

1. Advantages:

  • Good scalability

    Add new functions to the elements in the object structure without modifying the elements in the object structure.

  • Good reusability

    The general functions of the whole object structure are defined by visitors, so as to improve the degree of reuse.

  • Separation independent behavior

    The irrelevant behaviors are separated by visitors, and the relevant behaviors are encapsulated to form a visitor, so that the function of each visitor is relatively single.

2. Disadvantages:

  • Object structure changes are difficult

    In the visitor mode, every time a new element class is added, the corresponding specific operation must be added to each specific visitor class, which violates the "opening and closing principle".

  • Violation of the dependency inversion principle

    The visitor pattern relies on concrete classes instead of abstract classes.

5. Usage scenario

  • Object structure is relatively stable, but its operation algorithm often changes.

  • The objects in the object structure need to provide a variety of different and irrelevant operations, and the changes of these operations should not affect the object structure.

6. Expansion

The visitor pattern uses a double dispatch technique.

1. Distribution:

The type of a variable when it is declared is called the static type of the variable, and some people call the static type explicit type; The real type of the object referenced by the variable is also called the actual type of the variable. For example, Map = new hashmap(), the static type of the Map variable is Map, and the actual type is HashMap. The selection of methods according to the type of object is dispatch, which is divided into two types: static dispatch and dynamic dispatch.

Static dispatch occurs at compile time, and dispatch occurs according to static type information. Static dispatch is not new to us. Method overloading is static dispatch.

Dynamic dispatch occurs at runtime, and dynamic dispatch dynamically replaces a method. Java supports dynamic dispatch through method rewriting.

2. Dynamic dispatch:

Dynamic dispatch is supported through method rewriting.

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 said directly. Isn't this polymorphism! The run executes the methods in the subclass.

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; The method is called according to the real type of the object, not the static type.

3. Static distribution:

Static dispatch is supported through method overloading.

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);
    }
}

Operation results:

This result may surprise some people. Why?

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

4. Double dispatch:

The so-called double dispatch technology is to select a method not only according to the runtime of the message receiver, but also according to the runtime of the parameters.

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 above code, the client passes the Execute object as a parameter to the method called by the variable of Animal type. Here, the first dispatch is completed, and here is method rewriting, so it is dynamic dispatch, that is, Execute the method in the actual type, and also pass its own this as a parameter. Here, the second dispatch is completed, There are multiple overloaded methods in the Execute class, and the transfer is this, which is a specific object of actual type.

At this point, we already know what double dispatch is, but what effect does it have? That is, we can implement the dynamic binding of methods. We can modify the above program.

The operation results are as follows:

The essence of double dispatch to realize dynamic binding is to add the coverage link in the inheritance system in front of the overloaded method delegation. Because the coverage is dynamic, the overload is dynamic.

This blog comes from the summary of dark horse programmer's video tutorial and the arrangement of notes. It is only for learning and communication. It should not be used for commercial purposes. If there is infringement, please contact the blogger to delete it. Blogger QQ: 194760901

Topics: Design Pattern