This is the sixth day of my participation in the August More text Challenge. For details, see: August More Text Challenge

0 x0, introduction

😁 Wednesday has come, can the weekend be far behind? Continuing to explore the beauty of design patterns, this paper corresponds to design patterns and patterns: behavior patterns (68-69), and Visitor patterns, which are used to decouple object structures from object operations.

The difficulty is that the code implementation is complex because most object-oriented programming languages are statically bound. Which overloaded function of the class to call is determined by the type of the function declaration at compile time and not by the actual type of the arguments at run time.

The code implementation is difficult to understand and applying this pattern to a project can result in poor readability. It is not recommended to use this pattern unless it is particularly necessary

Of course, it is just as well to understand the next, in case the dish is really met with others with this mode, but also not a face meng meng ~

Tips: Second-hand knowledge processing is hard to avoid mistakes, interested in time can be consulted by themselves, thank you.


0 x1, definition

The original definition

Allows one or more operations to be applied to a set of objects at run time, separating the operations from the object structure.

Simply said

A group of objects that may differ in structure but must be connected at a central point or set of operations, i.e. :

Using the behavior (an operation) as the starting point for the extension object, batch extend the existing class without changing the functionality.

0x2. Write a simple example

Take automobile structure as an example, which contains engine, body and so on. People with different roles can have different access to these structures, such as:

  • Driver → view
  • Car wash guy → clean
  • Maintenance guy → check maintenance

Without visitors, the code implements a wave:

public abstract class AbstractVisitor {
    protected String name;
    public AbstractVisitor(String name) { this.name = name; }
    abstract void visitorEngine(a);
    abstract void visitorBody(a);
}

public class DriverVisitor extends AbstractVisitor {
    public DriverVisitor(String name) { super(name); }
    @Override void visitorEngine(a) { System.out.println(name + "→ View engine"); }
    @Override void visitorBody(a) { System.out.println(name + "→ View car body"); }}public class CleanerVisitor extends AbstractVisitor {
    public CleanerVisitor(String name) { super(name); }
    @Override void visitorEngine(a) { System.out.println(name + "→ Clean engine"); }
    @Override void visitorBody(a) { System.out.println(name + "→ Car body cleaning"); }}public class RepairVisitor extends AbstractVisitor {
    public RepairVisitor(String name) { super(name); }
    @Override void visitorEngine(a) { System.out.println(name + "→ Repair engine"); }
    @Override void visitorBody(a) { System.out.println(name + "→ Repair car body"); }}// Test case
public class Test {
    public static void main(String[] args) {
        List<AbstractVisitor> visitors = new ArrayList<>();
        visitors.add(new DriverVisitor("Jacob"));
        visitors.add(new CleanerVisitor("Wang"));
        visitors.add(new RepairVisitor("Hammer"));
        for(AbstractVisitor visitor: visitors) { visitor.visitorEngine(); visitor.visitorBody(); }}}Copy the code

The code runs as follows:

Yes, but there is a question

  • If you want to add functionality, such as support for access to tires, then all classes have to be changed, violating the open closed principle;
  • The access logic is coupled to the visitor class, resulting in their responsibilities are not simple enough to become a large hybrid;

Decouple business operations from specific data structures, design them into separate classes, and refactor the above code with visitor mode objects:

// Abstract the visitor
public abstract class AbstractVisitor {
    protected String name;
    public AbstractVisitor(String name) { this.name = name; }}// The specific visitor
public class DriverVisitor extends AbstractVisitor {
    public DriverVisitor(String name) { super(name); }}public class CleanerVisitor extends AbstractVisitor {
    public CleanerVisitor(String name) { super(name); }}public class RepairVisitor extends AbstractVisitor {
    public RepairVisitor(String name) { super(name); }}// Object structure
public class Car {
    public void visitorEngine(DriverVisitor visitor) {
        System.out.println(visitor.name + "→ View engine");
    }

    public void visitorEngine(CleanerVisitor visitor) {
        System.out.println(visitor.name + "→ Clean engine");
    }

    public void visitorEngine(RepairVisitor visitor) {
        System.out.println(visitor.name + "→ Repair engine");
    }

    public void visitorBody(DriverVisitor visitor) {
        System.out.println(visitor.name + "→ View car body");
    }

    public void visitorBody(CleanerVisitor visitor) {
        System.out.println(visitor.name + "→ Car body cleaning");
    }

    public void visitorBody(RepairVisitor visitor) {
        System.out.println(visitor.name + "→ Repair car body"); }}// Test case
public class Test {
    public static void main(String[] args) {
        List<AbstractVisitor> visitors = new ArrayList<>();
        visitors.add(new DriverVisitor("Jacob"));
        visitors.add(new CleanerVisitor("Wang"));
        visitors.add(new RepairVisitor("Hammer"));
        Car car = new Car();
        for(AbstractVisitor visitor: visitors) {
            // Failed to compile herecar.visitorEngine(visitor); car.visitorBody(visitor); }}}Copy the code

The above code is theoretically feasible, but using function overloading to call the corresponding method according to the parameter type is actually impossible to compile!

The reason is:

Function overloading in Java is a static binding that does not get the actual type of an object at compile time, but instead executes the corresponding method based on the declaration type.

The solution is to abstract the behavior/business into a separate class, pass the function to different visitors, perform the corresponding operation according to different visitor input parameters, visitors from active to passive, so as to avoid the compilation failure problem. Then the code implements a wave:

Accessing roles, engines, and bodies, passing in different visitors, performing different actions:

// Abstract access to the role class
public interface Visit {
    void visit(DriverVisitor visitor);
    void visit(CleanerVisitor visitor);
    void visit(RepairVisitor visitor);
}

// Access the role concrete implementation class
public class Engine implements Visit {
    @Override public void visit(DriverVisitor visitor) {
        System.out.println(visitor.name + "→ View engine");
    }

    @Override public void visit(CleanerVisitor visitor) {
        System.out.println(visitor.name + "→ Clean engine");
    }

    @Override public void visit(RepairVisitor visitor) {
        System.out.println(visitor.name + "→ Repair engine"); }}public class Body implements Visit {
    @Override public void visit(DriverVisitor visitor) {
        System.out.println(visitor.name + "→ View car body");
    }

    @Override public void visit(CleanerVisitor visitor) {
        System.out.println(visitor.name + "→ Car body cleaning");
    }

    @Override public void visit(RepairVisitor visitor) {
        System.out.println(visitor.name + "→ Repair car body"); }}Copy the code

Next comes the visitor, which defines the access operations for all access roles:

// Abstract visitor class
public abstract class AbstractVisitor {
    protected String name;
    public AbstractVisitor(String name) { this.name = name; }
    abstract void visitorEngine(Engine engine);
    abstract void visitorBody(Body body);
}

// The specific visitor class
public class DriverVisitor extends AbstractVisitor {
    public DriverVisitor(String name) { super(name); }
    @Override void visitorEngine(Engine engine) { engine.visit(this); }
    @Override void visitorBody(Body body) { body.visit(this); }}public class CleanerVisitor extends AbstractVisitor {
    public CleanerVisitor(String name) { super(name); }
    @Override void visitorEngine(Engine engine) { engine.visit(this); }
    @Override void visitorBody(Body body) { body.visit(this); }}public class RepairVisitor extends AbstractVisitor {
    public RepairVisitor(String name) { super(name); }
    @Override void visitorEngine(Engine engine) { engine.visit(this); }
    @Override void visitorBody(Body body) { body.visit(this); }}Copy the code

Moving on to the test case:

public class Test {
    public static void main(String[] args) {
        List<AbstractVisitor> visitors = new ArrayList<>();
        visitors.add(new DriverVisitor("Jacob"));
        visitors.add(new CleanerVisitor("Wang"));
        visitors.add(new RepairVisitor("Hammer"));
        // Instantiate the access role
        Engine engine = new Engine();
        Body body = new Body();
        for(AbstractVisitor visitor: visitors) { visitor.visitorEngine(engine); visitor.visitorBody(body); }}}Copy the code

The code run result is the same as above, can, but also exist to add new business, each visitor has to change the problem, but also to change ~

// Abstract visitor class
public class RepairVisitor extends AbstractVisitor {
    public RepairVisitor(String name) { super(name); }
    @Override void accept(Visit visit) { visit.visit(this); }}// The specific visitor class
public class DriverVisitor extends AbstractVisitor {
    public DriverVisitor(String name) { super(name); }
    @Override void accept(Visit visit) { visit.visit(this); }}public class CleanerVisitor extends AbstractVisitor {
    public CleanerVisitor(String name) { super(name); }
    @Override void accept(Visit visit) { visit.visit(this); }}public class RepairVisitor extends AbstractVisitor {
    public RepairVisitor(String name) { super(name); }
    @Override void accept(Visit visit) { visit.visit(this); }}// Test case
public class Test {
    public static void main(String[] args) {
        List<AbstractVisitor> visitors = new ArrayList<>();
        visitors.add(new DriverVisitor("Jacob"));
        visitors.add(new CleanerVisitor("Wang"));
        visitors.add(new RepairVisitor("Hammer"));
        // Instantiate the access role
        Engine engine = new Engine();
        Body body = new Body();
        for(AbstractVisitor visitor: visitors) { visitor.accept(engine); visitor.accept(body); }}}Copy the code

After the change, at this time we want to add a Visit to the tire business, just let it implement Visit interface, instantiate directly accept() can, do not change the visitor role code, wonderful!!

With UML class diagrams, component roles, usage scenarios, and pros and cons:

  • Visitor → Define access operations that declare all access roles;
  • ConcreteVisitor → Implement all access methods implemented in the abstract visitor;
  • Element (abstract access role) → Define methods to receive specific visitors;
  • ConcreteElement → Implement abstract access to roles and implement concrete operations;
  • ObjectStructure → alternative, so that visitors can access the corresponding elements;

Usage scenarios

  • The object data structure is relatively stable, but the operation is constantly changing;
  • Separate data structures from less common operations;
  • Dynamically decide which objects and methods to use at run time;

Advantages:

  • Meet the open-close principle and single responsibility principle;
  • Scalability, adding new access operations and visitors is very convenient;

disadvantages

  • Not suitable for scenarios where the structure changes frequently;
  • When the specific access role changes, the code needs to be modified, which may cause problems.

0x3. Extra meals: Single dispatch and double dispatch concepts

(1) single dispatch

  • Which object’s methods are executed, depending on the runtime type of the object;
  • Which method of the object to execute depends on the compile-time types of the method parameters;

(2) double dispatch

  • Which object’s methods are executed, depending on the runtime type of the object;
  • Which method of the object to execute depends on the runtime type of the method parameters;

Java function overload, which overloaded function is called, depends on the declaration type (compiler time type) of the incoming parameter, so it only supports single dispatch.

The example above uses the observation pattern to implement double dispatch indirectly, but it can also be implemented in other ways, such as the factory method pattern


That’s all for this section. Thank you