Overview of visitor Patterns

The Visitor pattern is a design pattern that separates data operations from data structures. It is used infrequently, but is often encountered in AOP development.

Design idea:

The software system has a stable object structure composed of many objects. The classes of these objects all have an Accept method to receive the access of visitor objects. A visitor is an interface that has a visit method that treats different types of elements in the object structure it visits. Object in an interview, we traverse the entire object structure, implement the accept method, for each element in each element of the accept method calls the visitor’s visit, and visitors to processing object structure of each element, we can according to the object structure design different visitors to complete the operation, To achieve the effect of differential treatment.

Usage Scenarios:

  • The object structure is stable, but it is often necessary to define new operations on this object structure.
  • You need to do many different and unrelated operations on objects in an object structure, and you want to avoid those operations’ contaminating ‘the classes of those objects, and you don’t want to modify those classes when you add new operations.

Compile-time annotations

Annotations are increasingly used in Android development and can be divided into runtime annotations and compile-time annotations, depending on the processing period. Runtime annotations have been criticized for performance issues (using reflection causes performance issues). The core principle of compile-time annotations relies on APT (Annotation Processing Tools) implementation. Dagger, ARouter, ButterKnife, etc are all based on this. How do compiler annotations use the visitor pattern?

How compile-time annotations work

At compile time, the compiler checks subclasses of AbstractProcessor and calls the process method of that type. Then it passes all the annotated elements to the process method so that the developer can process them at compile time. For example, new Java classes can be generated from annotations or code elements can be manipulated directly.

When processed at compile time, it is done separately. If a new Java source file is generated in one process, another process is needed to process the newly generated source file, and so on until no new files are generated. After the processing is complete, the Java code is compiled.

To compile a Java source file into a class file, there are three steps:

  1. Parse all the source code into a syntax tree and enter it into the compiler’s symbol table
  2. Annotation processor the process of processing annotations
  3. Parse the syntax tree and generate bytecode
  • Parse and Enter:

Lexical analysis: The source code character stream is parsed into Token stream by Scanner

Syntax analysis: Construct an abstract syntax tree (AST) from the token stream, using Treemaker, with subclasses of JCTree as syntax nodes.

Input symbols from A Java class into a symbol table: A symbol table is a table made up of a group of symbols and symbol information. In parsing, the contents of the symbol table are used for semantic checking and intermediate code generation. In the object code generation stage, symbol table is the basis of address allocation for symbol names.

AST (Abstract Syntax Tree) : a Tree representation used to describe the Syntax structure of a program code. Each node in the Syntax Tree indicates a Syntax structure in the program code, such as package, type, modifier, operator, interface, and return value.

  • Annotation Processing

Java provides a standard API for a set of plug-in annotation handlers that process annotations at compile time; During annotation processing, the entire abstract syntax tree can be retrieved and modified. Once the tree is modified, the compiler will go back to parsing and filling the symbol table until no more modifications have been made to the tree by any plug-in annotation processor.

  • Analyse And Generte

Semantic analysis: The main task is to conduct context-sensitive audits of well-structured source programs.

Interpretive sugar: Restores simple basic grammatical structures

Bytecode generation: The information generated in the previous steps (syntax tree, symbol table) is converted into bytecode and output to the class file.

Mirror API and Element code Element

Mirror API(com.sun. Mirror.*) is used to describe the static structure of the program at compile time. Mirror API can obtain the annotated Java type element information, thus providing the corresponding processing logic, specific processing work to APT tools to complete.

For example, the basic elements that make up code are packages, classes, functions, fields, type parameters, variables, etc. The JDK defines a base class for these elements, Element, which has the following subclasses

  • PackageElement: a PackageElement that contains information about a package, including the package name
  • TypeElement: Type elements, such as a field of a certain type
  • Executableelements: Executable elements that represent function-type elements
  • VariableElement: VariableElement
  • TypeParameterElement: TypeParameterElement

Public Element extends AnnotatedConstruct {ElementKind getKind(); // Get Modifier (public, static, final, etc.) Set<Modifier> getModifier (); . <R, P> R accept(ElementVisitor<R, P> visitor, P P); }Copy the code

Element defines a generic interface for code elements. Look at the Accept () function, which receives an ElementVisitor and a parameter of type P, which is used to pass some additional parameters to the Visitor.

Look at the ElementVisitor:

Public interface ElementVisitor<R, P> {// Visitor R visit(Element var1, P var2); R visitPackage(PackageElement var1, P var2); // access TypeElement R visitType(TypeElement var1, P var2); R visitVariable(VariableElement var1, P var2); ExecutableElement R visitExecutable(ExecutableElement var1, P var2); R visitTypeParameter(TypeParameterElement var1, P var2); // Handle unknown Element type R visitUnknown(Element var1, P var2);Copy the code

A typical visitor pattern is defined in ElementVisitor with multiple VISIT interface functions, each of which handles an element type.

A class element and a function element are completely different. Their structure is different, so the compiler must act differently on them. The visitor pattern solves the problem of separating data structures from data operations, preventing operations from ‘polluting’ the data object class.

When the Visitor accesses the element structure, it can do different processing for different types

conclusion

Compiler code abstract into a code element tree, and then at compile time for the whole tree traversal access, each element has a accept method receives the visitor access, each visitor has a corresponding visit function, at the end of each visit different processing methods for different types, so that to reach the effect of differences in processing, The separation of data structures and data operations makes the responsibilities of each type more singular.