In the process of software development, there are often different business operations on a data structure (object data), which are accessed by different methods. For example, we operate a user object in a permission management system, such as user name query, change, user integral query, user permission query and so on. The principle of a single responsibility for different businesses. They’re all different operations on the same object.

In real life, for example, we use an application to watch a movie and give comments and ratings on the movie. Every user’s comments are definitely different, but they are all about the movie. The data being processed is relatively stable and unique, and there are many ways to access it, which is easy to handle using the design pattern I’ll cover today (the Visitor pattern).

The visitor mode can separate the processing method from the data structure, and can add new processing method according to the need, and need not modify the original program code and data structure, which improves the scalability and flexibility of the program.

Definition and structural characteristics

Definition of visitors (Visitor) model: will work on a certain data structure of each element in the separate operation encapsulated into a separate class, to make it without change the structure of the data can be added to the new operating elements, each element offers a variety of access to the data structure, it will be to the operation of the data and the data structure of separation.

Structure and implementation of the visitor pattern

As you can see from the previous definition, the structure of a visitor is to separate elements (action resources) from operations and encapsulate them into separate classes.

  1. Pattern structure

The visitor pattern contains the following primary roles.

  • Abstract Visitor role: Defines an interface to access a concrete element, with an access operation visit() for each concrete element class, whose parameter types identify the concrete element to be accessed.
  • ConcreteVisitor role: Implements the individual access operations declared in the abstract visitor role, determining what a visitor should do when accessing an element.
  • Abstract Element role: Declares an interface that contains the accept() operation, with the accepted visitor object as an argument to the Accept () method.
  • ConcreteElement role: implements the accept() operation provided by the abstract element role. The method body is usually visit.visit (this). The ConcreteElement may also contain operations related to its own business logic.
  • A role is a container that contains element roles and provides methods for visitor objects to traverse all elements in the container, usually implemented by collection classes such as List, Set, Map, etc.

2. Visitor pattern structure diagram

Code case
1. Case 1
  • Abstract visitor
public interface Visitor {
	// Provide different access element interfaces
	void visit(ConcreteElementA element);
    void visit(ConcreteElementB element);
}
Copy the code
  • Specific Visitor A
public class ConcreteVisitorA implements Visitor {

	// Implement different access element implementations
	@Override
	public void visit(ConcreteElementA element) {
		System.out.println("Visitor A, visit" + element.operation());
	}

	@Override
	public void visit(ConcreteElementB element) {
		System.out.println("Visitor A, visit"+ element.operation()); }}Copy the code
  • Specific Visitor B
public class ConcreteVisitorB implements Visitor{

	@Override
	public void visit(ConcreteElementA element) {
		System.out.println("Visitor B, access" + element.operation());
	}

	@Override
	public void visit(ConcreteElementB element) {
		System.out.println("Visitor B, access"+ element.operation()); }}Copy the code
  • Abstract elements
// Abstract elements
public interface Element {
	
	// Generally provide an interface to accept visitors
	void accept(Visitor visitor);
}
Copy the code
  • Concrete element A
public class ConcreteElementA implements Element {

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

	public String operation(a){
		return "Concrete element A"; }}Copy the code
  • Concrete element B
public class ConcreteElementB implements Element{

	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}
	public String operation(a){
		return "Concrete element B"; }}Copy the code
  • Object structure

This is a very special role, and it’s specified here, the object structure is to maintain multiple elements, and pass visitors through the elements as you do so.

public class ObjectStructure {
	
	private List<Element> list = new ArrayList<Element>();
    
	public void accept(Visitor visitor) {
        Iterator<Element> i = list.iterator();
        while(i.hasNext()) { ((Element) i.next()).accept(visitor); }}public void add(Element element) {
        list.add(element);
    }
    public void remove(Element element) { list.remove(element); }}Copy the code
  • Use the client
public class Client {
	
	public static void main(String[] args) {
		ObjectStructure objectStructure = new ObjectStructure();
		// Add two access elements A\B
		objectStructure.add(new ConcreteElementA());
		objectStructure.add(new ConcreteElementB());
		
		ConcreteVisitorA concreteVisitorA = newConcreteVisitorA(); objectStructure.accept(concreteVisitorA); }}Copy the code

Visitor A accesses the concrete element A

Visitor A accesses the concrete element B

2. Case 2

Case 2, here write a project in the actual situation of the application. First of all, in our (risk control) project, the rule element object in the rule engine contains elements (attributes) of many rules to make decisions on the conditions in the rules, determine whether the rules are met, and do the before-and-after operations after hitting. For example, the condition that triggers the rule, the condition information that configures the rule, the post-operation after the rule hits, and so on. Here is a simple class diagram to show the attributes of a rule element.

In the process of development, we need to parse the rules configured in which fields, rules of operation (hit) after the configuration details of the rules of the trigger configuration and so on, these attributes belong to rule attribute of the element, to direct and various properties of parsing rules and if write different interface method, element to operate the current rules, coupling is very poor. And if the rule elements do related operations, but also add interface methods, data access and specific elements are not isolated, not easy to expand.

Take a look at the best practices in the project through code. First the rule inherits the abstract interface as a concrete element.

  • Abstract elements
public interface Visitable {
    void accept(Visitor var1);
}
Copy the code
  • Concrete elements (rules)
public class Rule Visitable {
    // ...
    /** * Rule condition configuration **@see RuleCondition
     */
    private RuleCondition condition;


    /** * Rule operation configuration **@see RuleAction
     */
    private List<RuleAction> actions;
    
    /** * Rule triggers operation */
    private String triggers;
    
    / /... slightly
    
// Accept accepts visitors
@Override
    public void accept(Visitor visitor) {
        visitor.visit(this); }}Copy the code
  • Abstract visitor
public interface Visitor<T> {
    void visit(T var1);
}
Copy the code
  • Specific visitor

The code is not posted here, let’s look at some implementation classes.

  • Using the

Call directly from the concrete element (rule)Accept ()Receives visitors, and the specific operations on elements are performed in visitors.

It then retrieves the results of the visitor’s actions on the element

The above is the scenario application in the actual combat of the project. If we add new requirements later, we can directly expand the visitors without changing the existing code logic.

Advantages, disadvantages and application scenarios of the visitor pattern

1. Advantages:

  • Good scalability. The ability to add new functionality to elements in an object structure without modifying them.
  • Good reusability. Visitors can be used to define common functions throughout the object structure, thereby increasing the reuse of the system.
  • Good flexibility. The visitor pattern decouples the data structure from the operations acting on it, allowing the set of operations to evolve relatively freely without affecting the system’s data structure.
  • Consistent with the principle of single responsibility. The visitor pattern encapsulates related behaviors into a single visitor, making each visitor’s function relatively single.

2. Disadvantages:

  • Adding new element classes is 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.”
  • Break encapsulation. Specific elements in the visitor pattern expose details to visitors, breaking the encapsulation of objects.
  • It violates the dependency inversion principle. The visitor pattern relies on concrete classes, not abstract ones.
Application scenarios for the visitor pattern

Use the visitor pattern when there are multiple different operations on an element.

The Visitor pattern is usually considered in the following situations.

  • 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.
  • An object structure contains objects of many types, and you want to perform operations on those objects that depend on their specific type.

Use of the visitor pattern in source code

In the Spring container, the BeanDefinition interface is used to store the basic information of spring beans. This includes bean name, bean class, scope, lazy loading of isLazyInit, etc. Implementation classes commonly used mainly include RootBeanDefinition, ChildBeanDefinition, GenericBeanDefinition, etc.

In Spring Beans there is a BeanDefinitionVisitor class with a set of supplementary information for the BeanDefinition object, such as the source code:

public class BeanDefinitionVisitor {
    @Nullable
    private StringValueResolver valueResolver;

    public BeanDefinitionVisitor(StringValueResolver valueResolver) {
        Assert.notNull(valueResolver, "StringValueResolver must not be null");
        this.valueResolver = valueResolver;
    }

    protected BeanDefinitionVisitor(a) {}public void visitBeanDefinition(BeanDefinition beanDefinition) {
        // A list of visitor methods
        this.visitParentName(beanDefinition);
        this.visitBeanClassName(beanDefinition);
        this.visitFactoryBeanName(beanDefinition);
        this.visitFactoryMethodName(beanDefinition);
        this.visitScope(beanDefinition);
        if (beanDefinition.hasPropertyValues()) {
            this.visitPropertyValues(beanDefinition.getPropertyValues());
        }

        if (beanDefinition.hasConstructorArgumentValues()) {
            ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
            this.visitIndexedArgumentValues(cas.getIndexedArgumentValues());
            this.visitGenericArgumentValues(cas.getGenericArgumentValues()); }}protected void visitBeanClassName(BeanDefinition beanDefinition) {
        String beanClassName = beanDefinition.getBeanClassName();
        if(beanClassName ! =null) {
            String resolvedName = this.resolveStringValue(beanClassName);
            if(! beanClassName.equals(resolvedName)) { beanDefinition.setBeanClassName(resolvedName); }}}protected void visitScope(BeanDefinition beanDefinition) {
        String scope = beanDefinition.getScope();
        if(scope ! =null) {
            String resolvedScope = this.resolveStringValue(scope);
            if(! scope.equals(resolvedScope)) { beanDefinition.setScope(resolvedScope); }}}/ / a little...
Copy the code

The visitor pattern here uses a simple BeanDefinition object as an element, and the different methods in the visitor complement the BeanDefinition object. There is no element abstraction, visitor abstraction and other design.