This article introduces the concept and usage of the visitor pattern.

Model background

In a system, sometimes a series of objects need to be accessed in different ways. For example, for prescriptions written by the hospital, in the hospital finance the pharmacy is just used to calculate the price, and in the prescription where the prescription is used to fill the medicine. Analogies to actual project use: there may be several types of elements in a collection, and different types of elements can be accessed by different visitors and have different access execution logic. Without a good schema support, we might need to use a lot of if-else in a class to differentiate between different visitors to separate them. But the problem is obvious: classes are heavy, a lot of if-else code is definitely not elegant, and this approach is basically unscalable once visitors or accessed objects need to be modified/added/removed, etc. So the visitor pattern provides the best way to add new visitors without changing the code.

Definitions & Concepts

Visitor pattern: Provides a representation of operations that act on elements in an object structure, allowing new operations to be defined on those elements without changing their classes. The visitor pattern is an object behavior pattern.

The principle of

The Visitor pattern is a more complex design pattern. The overall structure can be divided into two layers: the access layer, the element layer (also can be said to be accessed layer). The idea is to get rid of if-else and encapsulate different operations into different visitor object classes. To facilitate extension, an abstraction layer is provided for both elements and visitors, and at the element layer, we need to organize the structure of the elements through an object structure, which can be understood as a collection that defines the structure of these elements.

Take a look at their constituent elements, and notice the two best bits: accept and visit methods.

elements

  • Abstract Visitor
    • Mainly for the expansion of visitors.
    • It defines a series ofvisitMethod, which can be distinguished by method names or overloaded with parameters, that specifies the different operation logic that is required by the visitor to the different element objects.Typically, one visit method is provided for each element.
  • ConcreteVisitor
    • Implement abstract access logic.
  • Abstract Elements
    • To define aacceptMethods, parameters are usually abstract visitors.
  • ConcreteElement
    • implementationacceptCompletes what you want to do to access an element. The essence is to call the method of the passed abstract visitor (that is, to call the visitor’s visit method).
    • This mechanism, also known as double dispatch, takes advantage of parameter overloading to allow additional visitors to pass calls this way (calling corresponding methods via parameter overloading) without modifying any code.
  • ObjectStructure
    • Used to store element objects. And provides a way to traverse the inner elements.
    • This can be done using composite patterns. It can also be a simple collection object.

To summarize his idea, something called an object structure encapsulates the structure of a set, and then the concrete elements are pulled out to form a system. Visitors are another system. In the visitor system, each visitor implements a visit method for the required operation, which is later called by passing itself to a concrete element object and then calling it from that element object!

The complexity is in the design of the visit and Accept methods, which is really neat, but it does make the structure less intuitive.

UML

implementation

Abstract visitor

public interface Visitor {
    void visit(ConcreteElementA concreteElementA);
    void visit(ConcreteElementB concreteElementB);
}
Copy the code

Specific visitor

public class ConcreteVisitorA implements Visitor {
    @Override
    public void visit(ConcreteElementA concreteElementA) {
        System.out.println("A visit:" + concreteElementA.getName());
    }

    @Override
    public void visit(ConcreteElementB concreteElementB) {
        System.out.println("A visit:"+concreteElementB.getName()); }}Copy the code

Abstract elements

public interface Element {
    /** * This accept passes an ACCEPT to the elements of the class. All elements in the collection are processed with the visitor. * This visitor implements the visit element method. So passing this can be overridden into the implementation logic@param visitor
     */
    void accept(Visitor visitor);
}
Copy the code

Specific elements

public class ConcreteElementA implements Element {

    private String name;

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

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public String getName(a) {
        returnname; }}Copy the code

Object structure

public class ObjectStructure {

    /** * use collection collection to hold all elements. * /
    private List<Element> list = new ArrayList<>();

    public void addElement(Element e) {
        list.add(e);
    }

    public void removeElement(Element e) {
        list.remove(e);
    }

    /** * uses a visitor to traverse the collection element */
    public void accept(Visitor visitor) { list.forEach(item -> item.accept(visitor)); }}Copy the code

use

Element e1, e2, e3, ee1, ee2, ee3;
ObjectStructure objectStructure = new ObjectStructure();
e1 = new ConcreteElementA("A1");
e2 = new ConcreteElementA("A2");
e3 = new ConcreteElementA("A3");
ee1 = new ConcreteElementA("B1");
ee2 = new ConcreteElementA("B2");
ee3 = new ConcreteElementA("B3");
objectStructure.addElement(e1);
objectStructure.addElement(e2);
objectStructure.addElement(e3);
objectStructure.addElement(ee1);
objectStructure.addElement(ee2);
objectStructure.addElement(ee3);
// You can configure which access class to use
Visitor v = new ConcreteVisitorA();
objectStructure.accept(v);
Copy the code

The advantages and disadvantages

advantages

  • The responsibilities of each role are isolated from each other, in line with the principle of single responsibility.
    • Visitor, Element and ObjectStructure each have their own responsibilities and clear responsibilities.
  • Good scalability, adding new visitors do not need to modify the original code, for visitors, in line with the open and closed principle.

disadvantages

  • It does not solve the problem of adding an element class. Once you add an element class, you have to modify all relevant visitors, which violates the open close principle.
  • It may break the encapsulation of element classes, because the pattern requires the visitor object to invoke specific operations on the element object, so the element object may need to expose some of its internal state to cooperate with the visitor object to complete the operation.

Usage scenarios

The conditions used by visitors are more demanding and the structure is very complex, so the frequency of actual application is not high. The visitor pattern can be used when you have a complex object structure in your system and there are different visitors and the operations to access them are different.

Existing some practical applications: XML document parsing, compiler design and so on.

conclusion

We evaluate the suitability of the visitor pattern on a case-by-case basis, for example, whether our object structure is stable enough, whether we need to define new operations frequently, and whether using the visitor pattern will optimize our code rather than make it more complex.

The attached

Related code: github.com/zhaohaoren/…

If there are code and article problems, also please correct! Thank you!