Visitor model -- the generation gap between young and middle-aged people

Keywords: Design Pattern computer

Introduction

Xiaoshuai is 25 years old and Lao Wang is 35 years old. One day, Xiaoshuai and Lao Wang discussed the increasingly obvious difference between young and middle-aged people.

Xiao Shuai listed several scenarios. At the same time, Xiao Shuai, as the representative of young people, and Lao Wang, as the representative of middle-aged people, answered respectively.

1. What if you encounter something unpleasant at work and can't be solved immediately, such as the leader has prejudice against you or the work task is too heavy?

Xiaoshuai: you don't have to stay here. You have to stay here. Change your job immediately.
Lao Wang: first bear it, then endure it. Maybe sometimes it will be a day of operation. After all, there are so many mortgage loans to be repaid every month.

2. How to arrange the year-end bonus?

Xiaoshuai: go and buy the latest mobile phone or other electronic products. Have a good time. I'm drunk now.
Lao Wang: take it all to repay the mortgage.

3. How do you spend the weekend?

Xiaoshuai: two things, sleeping and playing games.
Lao Wang: two things, doing housework and taking care of children.

No wonder young people and middle-aged people can't play together. This is the generation gap.

Lao Wang said: we have said so much. Can you write the above dialogue in code?

Xiaoshuai: I really convinced you. Everything can be related to the code. OK, I'm in a good mood today. I'll write to you.

Human abstract class:

/**
 * Human abstract class
 */
public abstract class Person {
    /**
     * scene
     */
    protected String scene;

    public Person(String scene) {
        this.scene = scene;
    }

    /**
     * answer
     */
    public abstract void answer();
}

Young humans:

/**
 * young people
 */
public class Young extends Person{

    public Young(String scene) {
        super(scene);
    }

    @Override
    public void answer() {
        if("Work is not going well".equals(scene)) {
            System.out.println("Young man: you don't have to stay here. You have to stay here. Change your job immediately.");
        } else if("annual bonus".equals(scene)) {
            System.out.println("Young man: go and buy the latest mobile phone or other electronic products. Have a good time. I'm drunk now.");
        } else if("weekend".equals(scene)) {
            System.out.println("Young man: two things, sleeping and playing games.");
        }
    }
}

Middle aged humans:

/**
 * middle-aged person
 */
public class MiddleAged extends Person{

    public MiddleAged(String scene) {
        super(scene);
    }

    @Override
    public void answer() {
        if("Work is not going well".equals(scene)) {
            System.out.println("Middle aged man: first bear it, then endure it. Maybe sometimes it will be a day of operation. After all, there are so many mortgage loans to be repaid every month.");
        } else if("annual bonus".equals(scene)) {
            System.out.println("Middle aged man: take it all to repay the mortgage.");
        } else if("weekend".equals(scene)) {
            System.out.println("Middle aged man: two things, doing housework and taking care of children.");
        }
    }
}

Client class:

/**
 * client
 */
public class Client {

    public static void main(String[] args) {
        List<Person> personList = new ArrayList<>();
        personList.add(new Young("Work is not going well"));
        personList.add(new MiddleAged("Work is not going well"));
        personList.add(new Young("annual bonus"));
        personList.add(new MiddleAged("annual bonus"));
        personList.add(new Young("weekend"));
        personList.add(new MiddleAged("weekend"));

        personList.stream().forEach(f -> f.answer());
    }
}

Output:

Young man: you don't have to stay here. You have to stay here. Change your job immediately.
Middle aged man: first bear it, then endure it. Maybe sometimes it will be a day of operation. After all, there are so many mortgage loans to be repaid every month.
Young man: go and buy the latest mobile phone or other electronic products. Have a good time. I'm drunk now.
Middle aged man: take it all to repay the mortgage.
Young man: two things, sleeping and playing games.
Middle aged man: two things, doing housework and taking care of children.

After reading it, Lao Wang said: it's well written, but there are still some problems. What if I want to add a few more problem scenes, such as what books to read, what sports to like, and what time to sleep at night?

Xiaoshuai: it's not easy. Just add the if... else condition to the Young and MiddleAged classes!

Lao Wang shook his head and said: this violates the opening and closing principle: close to modification and open to expansion. If these are classes provided by a third party, you can't modify them, but what should you do to add new behaviors?

"How is it possible to add new behavior to existing classes without modifying existing code?" Xiaoshuai doubted.

Lao Wang said with a smile: why not? There is a design pattern that does this.

Visitor mode

Visitor mode: provides an operation that acts on each element in an object structure. You can define a new operation that acts on an element without changing the element class.

Visitor pattern is a behavior design pattern that allows you to add new behaviors to the existing class hierarchy without modifying the existing code.

  • Visitor (visitor, such as Scene)
    Abstract classes or interfaces that declare which elements visitors can access. Specifically, in the program, the getYoungAnswer method can access the young object; The getmiddleaged answer method can access the middleAged object.
  • ConcreteVisitor (specific visitors, such as WorkNotWell, YearEndAwards, Weekend)
    The implementation class of the visitor interface to implement specific operations.
  • Element (element, such as Person)
    Interface or abstract class, which defines an accept operation with an accessor as a parameter.
  • ConcreteElement (concrete elements, such as Young and MiddleAged)
    Implement the accept operation, usually in the visitor.visit(this) mode.
  • ObjectStructure (object structure)
    Store a collection of objects to facilitate traversal of the elements.

Scene class:

/**
 * scene
 */
public interface Scene {

    /**
     * Young man's answer
     * @param young
     */
    public void getYoungAnswer(Young young);

    /**
     * Middle aged man's answer
     * @param middleAged
     */
    public void getMiddleAgedAnswer(MiddleAged middleAged);
}

Bad work scenario:

/**
 * Bad work
 */
public class WorkNotWell implements Scene{

    @Override
    public void getYoungAnswer(Young young) {
        System.out.println(young.name + ": You don't have to stay here. You have to stay here. Change your job immediately.");
    }

    @Override
    public void getMiddleAgedAnswer(MiddleAged middleAged) {
        System.out.println(middleAged.name + ": Endure first and then endure. Maybe sometimes it will be a day of operation. After all, there are so many mortgage loans to repay every month.");
    }
}

Year end bonus scenario:

/**
 * annual bonus
 */
public class YearEndAwards implements Scene{

    @Override
    public void getYoungAnswer(Young young) {
        System.out.println(young.name + ": Go and buy the latest mobile phone or other electronic products. Have a good time. I'm drunk now.");
    }

    @Override
    public void getMiddleAgedAnswer(MiddleAged middleAged) {
        System.out.println(middleAged.name + ": Take it all to repay the mortgage.");
    }
}

Weekend scene:

/**
 * weekend
 */
public class Weekend implements Scene{

    @Override
    public void getYoungAnswer(Young young) {
        System.out.println(young.name + ": Two things, sleeping and playing games.");
    }

    @Override
    public void getMiddleAgedAnswer(MiddleAged middleAged) {
        System.out.println(middleAged.name + ": Two things, housework and baby.");
    }
}

Human interface:

/**
 * Human interface
 */
public interface Person {

    /**
     * accept
     */
    public void accept(Scene scene);
}

Young humans:

/**
 * young people
 */
public class Young implements Person{

    protected String name;

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

    @Override
    public void accept(Scene scene) {
        scene.getYoungAnswer(this);
    }
}

Middle aged humans:

/**
 * middle-aged person
 */
public class MiddleAged implements Person{

    protected String name;

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

    @Override
    public void accept(Scene scene) {
        scene.getMiddleAgedAnswer(this);
    }
}

Object structure class:

/**
 * Object structure
 */
public class ObjectStructure {

    private List<Person> personList = new ArrayList<>();

    /**
     * newly added
     * @param person
     */
    public void add(Person person) {
        personList.add(person);
    }

    /**
     * delete
     * @param person
     */
    public void delete(Person person) {
        personList.remove(person);
    }

    /**
     * Traversal display
     * @param scene
     */
    public void display(Scene scene) {
        personList.stream().forEach(f -> f.accept(scene));
    }
}

Client class:

/**
 * client
 */
public class Client {
    public static void main(String[] args) {
        ObjectStructure objectStructure = new ObjectStructure();
        objectStructure.add(new Young("Xiaoshuai"));
        objectStructure.add(new MiddleAged("Lao Wang"));

        // A scene where work is not going well
        WorkNotWell workNotWell = new WorkNotWell();
        objectStructure.display(workNotWell);

        // Year end bonus scenario
        YearEndAwards yearEndAwards = new YearEndAwards();
        objectStructure.display(yearEndAwards);

        // Weekend scene
        Weekend weekend = new Weekend();
        objectStructure.display(weekend);
    }
}

Output:

Xiaoshuai: you don't have to stay here. You have to stay here. Change your job immediately.
Lao Wang: first bear it, then endure it. Maybe sometimes it will be a day of operation. After all, there are so many mortgage loans to be repaid every month.
Xiaoshuai: go and buy the latest mobile phone or other electronic products. Have a good time. I'm drunk now.
Lao Wang: take it all to repay the mortgage.
Xiaoshuai: two things, sleeping and playing games.
Lao Wang: two things, doing housework and taking care of children.

Lao Wang said to Xiaoshuai: it can be realized by applying the visitor mode, adding new behaviors to the existing classes without modifying the existing code.

You see, for example, if I want to add a Scene of "what time to Sleep at night", I just need to add a Sleep class to implement the Scene interface.

/**
 * sleep
 */
public class Sleep implements Scene{
    @Override
    public void getYoungAnswer(Young young) {
        System.out.println(young.name + ": I didn't go to bed until 12:30. I'm energetic.");
    }

    @Override
    public void getMiddleAgedAnswer(MiddleAged middleAged) {
        System.out.println(middleAged.name + ": Go to bed at 10:30. It's good to go to bed early and get up early.");
    }
}

Then add relevant calling code in the Client class to realize this scenario:

// Sleeping scene
Sleep sleep = new Sleep();
objectStructure.display(sleep);
Xiaoshuai: I didn't sleep until 12:30. I'm energetic.
Lao Wang: go to bed at 10:30. It's good to go to bed early and get up early.

You see, it's amazing that you can add new operations without modifying the Young and MiddleAged classes at all?

Xiaoshuai is a little confused: it's very interesting, but this code looks a little strange. Why does the accept method pass in the scene object and then its own object this?

Isn't this equivalent to calling other people's methods and taking myself in?

@Override
public void accept(Scene scene) {
    scene.getYoungAnswer(this);
}

Single dispatch and double dispatch

Lao Wang laughed and said, "take yourself in." Xiaoshuai, your statement is very funny. I'll tell you what's going on.

How are the three scenarios WorkNotWell, welled and YearEndAwards associated with Young and MiddleAged?

In fact, there are two steps:

First, when calling the display method, a specific scene implementation class is passed in to determine the specific scene.

Second, this object is passed in the accept method, and the specific element object (Young) is also determined, so that both objects are determined.

Here, I also want to talk to you about the concepts of Single Dispatch and Double Dispatch, Lao Wang continued.

The so-called single dispatch refers to the method of which object to execute, which is determined according to the runtime type of the object; Which method of the object to execute depends on the compile time type of the method parameter.

The so-called double dispatch refers to the method of which object to execute, which is determined according to the runtime type of the object; Which method of the object to execute depends on the runtime type of the method parameter.

Let's start with a code:

public class Parent {
    public void f() {
        System.out.println("I am the method of the parent class f()");
    }
}
public class Child extends Parent{
    @Override
    public void f() {
        System.out.println("I am a subclass method f()");
    }
}

/**
 * Single dispatch
 */
public class SingleDispatch {
    public void show(Parent p) {
        p.f();
    }
    public void overloadFunction(Parent p) {
        System.out.println("I am a parent parameter overloaded method: overloadFunction(Parent p)");
    }
    public void overloadFunction(Child c) {
        System.out.println("I am a subclass parameter overloaded method: overloadFunction(Child c)");
    }
}

public class Test {
    public static void main(String[] args){
        SingleDispatch singleDispatch = new SingleDispatch();
        Parent p = new Child();
        singleDispatch.show(p);
        singleDispatch.overloadFunction(p);
    }
}

Output:

I am a subclass method f()
I am a parent parameter overloaded method: overloadFunction(Parent p)

Illustration of single dispatch:

Because Java is a single dispatch language, the method of which object to execute depends on the runtime type of the object. p.f() here runs the Child object, so it executes the method in the Child class.


Which method of the object to execute depends on the compile time type of the method parameter. Here, the compile time type is Parent.

Therefore, the public void overload function (parent P) method is called.

Because the Java language only supports single dispatch, it is necessary to use the visitor mode to implement double dispatch, which is why the implementation class of ConcreteElement should be used in the ConcreteVisitor class, rather than the Element interface.


Then, the method to be called is determined by passing in the this object in the accept method.

@Override
public void accept(Scene scene) {
    scene.getYoungAnswer(this);
}

summary

If the structure of an object is complex and the elements are stable and not easy to change, that is, the ConcreteElement class is stable and will not be added casually, but it is necessary to often define new operations on this structure, it is very suitable to use the visitor mode.

advantage

  • Comply with the opening and closing principle, and add new behavior to the existing class without modifying the existing code.
  • Centralizing the relevant behaviors into a visitor object simplifies the element class.

shortcoming

  • Adding a new ConcreteElement class is troublesome. Every time a new ConcreteElement class is added, new operations must be added to all Visitor classes.

If a new ConcreteElement class is always added, the Vistor class and its subclasses will become difficult to maintain. In this case, the visitor mode is suitable.

Visitor mode makes it easier for us to add access operations, but it is difficult to add elements, so visitor mode is suitable for structures with relatively stable elements.

Code link

Posted by Stanza on Sun, 12 Sep 2021 20:39:11 -0700