This is the 10th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

An overview of the

Definition:

Encapsulates operations on elements of a data structure that define new operations on those elements without changing the data structure.

structure

The visitor pattern contains the following key roles:

  • Abstract Visitor role: Defines the Visitor role for each element(Element)The number of methods is theoretically the same as the number of Element classes (the number of implementation classes of an Element). From this, it is easy to see that the visitor pattern requires that the number of Element classes cannot be changed.
  • ConcreteVisitor role: Gives the concrete behavior generated when accessing each element class.
  • Abstract Element role: Defines a method that accepts visitors (accept), meaning that every element should be accessible to visitors.
  • ConcreteElement role: Provides a concrete implementation of the method that receives access, typically using a method provided by the visitor to access the element class.
  • Role: The definition of an Object Structure. An Object Structure is an abstract representation of a class that has the properties of a container or a composite Object and contains a set of elements.Element), and you can iterate over these elements for visitors to access.

Case implementation

Feed the pet

Now there are many people who keep pets, let’s take this as an example, of course, pets are also divided into dogs, cats, etc., to feed the pets, the owner can feed, other people can also feed.

  • Visitor role: the person who feeds the pet
  • Specific visitor roles: host, others
  • Abstract element personas: Animal abstract classes
  • Specific elements role: pet dog, pet cat
  • Structure object role: host family

The 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);
}
Copy the code

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 master feeds the cat.");
    }

    @Override
    public void feed(Dog dog) {
        System.out.println("The master feeds the dog."); }}public class Someone implements Person {
    @Override
    public void feed(Cat cat) {
        System.out.println("The others feed the cat.");
    }

    @Override
    public void feed(Dog dog) {
        System.out.println("The others feed the dogs."); }}Copy the code

Define the abstract node — pet

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

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("Yummy, woof woof!!"); }}public class Cat implements Animal {

    @Override
    public void accept(Person person) {
        person.feed(this);
        System.out.println("Yum, meow, meow, meow!!"); }}Copy the code

Define the object structure, which in this case is the host’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 operations
    public void add(Animal animal) { nodeList.add(animal); }}Copy the code

The 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 = newSomeone(); home.action(someone); }}Copy the code

The advantages and disadvantages

1. Advantages:

  • Good scalability

    Adds new functionality to elements in an object structure without modifying them.

  • Good reusability

    Increase reuse by allowing visitors to define common functionality throughout the object structure.

  • Separate irrelevant behavior

    Separate unrelated behaviors through visitors, encapsulate related behaviors together to form a visitor, so that each visitor’s function is relatively single.

2. Disadvantages:

  • Object structure changes are difficult

    In the visitor pattern, for every new element class, a specific action is added to each specific visitor class, which violates the “open close principle.”

  • It violates the dependency inversion principle

    The visitor pattern relies on concrete classes, not abstract ones.

Usage scenarios

  • A program whose object structure is relatively stable but whose operating algorithm frequently changes.

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

extension

The visitor pattern uses a double dispatch technique.

1. Dispatch:

The type in which a variable is declared is called the static type of the variable. Some people call static types obvious types. The actual type of the object referenced by a variable is called the actual type of the variable. For example, Map Map = new HashMap(), where 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 Dispatch and dynamic Dispatch.

Static Dispatch occurs at compile time, and Dispatch occurs based on Static type information. Static dispatch is not new to us, and method overloading is static dispatch.

Dynamic Dispatch occurs at run time and dynamically swaps out 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(a) {
        System.out.println("Animal"); }}public class Dog extends Animal {
    @Override
    public void execute(a) {
        System.out.println("dog"); }}public class Cat extends Animal {
     @Override
    public void execute(a) {
        System.out.println("cat"); }}public class Client {
   	public static void main(String[] args) {
        Animal a = new Dog();
        a.execute();
        
        Animal a1 = newCat(); a1.execute(); }}Copy the code

The result of the above code we should be able to directly say, it is not polymorphic! A run executes a method in a subclass.

The Java compiler does not always know what code is being executed at compile time, because the compiler only knows the static type of the object, not the real type of the object; Methods are called based on the real type of the object, not the static type.

3. Static dispatch:

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 = newExecute(); exe.execute(a); exe.execute(a1); exe.execute(a2); }}Copy the code

Running results:

This may come as a surprise to some, but why?

Overloaded methods are dispatched based on static types, and the dispatch process is completed at compile time.

4. Double dispatch:

The so-called double dispatch technique is to select a method based not only on the runtime differences of the message receiver, but also on the runtime differences 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 = newExecute(); a.accept(exe); d.accept(exe); c.accept(exe); }}Copy the code

In the code above, the client will Execute objects as arguments passed to the Animal the method called types of variables, here to complete the first dispatch, here is the way to rewrite, so is the dynamic dispatch, also is to perform the actual type of the method, at the same time also this passed as a parameter in, here is completed the second assignment, There are multiple overloaded methods in the Execute class, and the pass is this, which is the object of the actual type.

So far, we’ve seen how double dispatch works, but what does it do? Is can realize the method of dynamic binding, we can modify the above procedure.

The running results are as follows:

The essence of double dispatch to achieve dynamic binding is to prefix the overloaded method delegate with the overridden link in the inheritance system. Since overloading is dynamic, overloading is dynamic.