preface
The visitor pattern uses a lot of scenarios, but the actual use is not many, difficult to understand is one aspect, this article mainly records the visitor pattern related learning content, through this article you can understand
- What is the visitor pattern, and what is the problem the visitor pattern is trying to solve?
- What are the classic applications of the visitor pattern?
What is the Visitor pattern?
Many design pattern documents give an abstract definition of visitor patterns that can be confusing. Let’s look at the problem the visitor pattern is trying to solve from the perspective of where it works. Before introducing design patterns, understand a few basic concepts. Understanding the meaning of a concept is not to be literal, but to understand in principle the problems and implications behind the design pattern.
In Java polymorphism, override, and overload
- Overrides, when a subclass overrides a method of its parent class, return values and parameters cannot be changed. When a subclass object calls an overridden method, it calls the subclass’s method, not the overridden method in its parent class.
- Overloading, a class in which methods have the same name but different parameters. The return types can be the same or different.
What are dispatching, single dispatching and double dispatching
- Dispatch. In object-oriented languages, a function Dispatch can be understood as the Dispatch of a message event, as in
a.test(b)
A is the receiver of the message, and the caller of this function is the sender of the message. - Single Dispatch, where Single means which object’s methods are executed, depends only on the object’s runtime type. In order to
a.test(b)
For example, in Java, the test function being executed depends only on the runtime type of the A object. - Double Dispatch, where Double means which object’s method is executed depends on the runtime type of both the object and method arguments. Or in the
a.test(b)
For example, which test function is executed depends not only on the type of object A but also on the type of object B.
It can be seen that the so-called dispatch is the call of the function, the so-called single dispatch and double dispatch is related to the polymorphic characteristics of the language, in the common Java, C++, C# language, in the language level is only support single dispatch. To achieve double dispatch, use design patterns, such as the visitor pattern. What, you ask, is the function of double dispatch? Can’t we not solve the problem of double dispatch? In fact, this problem must be common in the project code, like the following code, we want to perform different file advance and file compression operations for different types of files (PDF, PPT, Word). Think about it. How could you possibly handle it?
class Extractor extends Processor { void processFile(ResourceFile file) { if (file instanceof PdfFile) { processPrdFile((PdfFile)file); } else if (file instanceof PowerPointFile) { processPowerPointFile((PowerPointFile)e); } else if (file instanceof WordFile) { processWordFile((WordFile)e); } } } class Compressor extends Processor { void processFile(ResourceFile file) { if (file instanceof PdfFile) { processPrdFile((PdfFile)file); } else if (file instanceof PowerPointFile) { processPowerPointFile((PowerPointFile)e); } else if (file instanceof WordFile) { processWordFile((WordFile)e); }}}Copy the code
Is it easy to write code like this (partially omitting the definition of abstract classes and methods)? You can see the logical execution of this code depending on the runtime type of the receiver, which can be understood as the Processor object corresponding to the current method. Also according to the actual runtime type of ResourceFile file object to do the type judgment, there will be a lot of instanceof and else if,switch case multiple nesting. Although the code of this design can realize the function, it is very inflexibly in the face of requirement change and expansion, which requires a lot of else IF, and is not conducive to the cohesion and reuse of the function.
This double dispatch problem can be solved by using the visitor pattern, as shown in the class diagram above, to separate file access and processing into two separate interfaces by differentiating functions between several roles.
- Visitor refers to different types of file operations. Define different types of operations with an interface and a set of concrete implementations of different types.
- Element refers to different types of files. Defines the Accept operation, which takes Visitor as parameter to accept objects of different types of Visitor. Passing this to the visitor in the Accept method enables double dispatch by calling back and calling back again.
- ObjectStructure, the organizer of access, can be a combination or a collection; The ability to enumerate elements it contains; Provides an interface that allows Vistor to access its elements.
Here is an example code from Wang Zheng’s “Beauty of Design Patterns” course:
public abstract class ResourceFile { protected String filePath; public ResourceFile(String filePath) { this.filePath = filePath; } abstract public void accept(Visitor vistor); } public class PdfFile extends ResourceFile { public PdfFile(String filePath) { super(filePath); } @Override public void accept(Visitor visitor) { visitor.visit(this); } / /... } / /... PPTFile and WordFile are similar to PdfFile. public interface Visitor { void visit(PdfFile pdfFile); void visit(PPTFile pdfFile); void visit(WordFile pdfFile); } public class Extractor implements Visitor { @Override public void visit(PPTFile pptFile) { //... System.out.println("Extract PPT."); } @Override public void visit(PdfFile pdfFile) { //... System.out.println("Extract PDF."); } @Override public void visit(WordFile wordFile) { //... System.out.println("Extract WORD."); } } public class Compressor implements Visitor { @Override public void visit(PPTFile pptFile) { //... System.out.println("Compress PPT."); } @Override public void visit(PdfFile pdfFile) { //... System.out.println("Compress PDF."); } @Override public void visit(WordFile wordFile) { //... System.out.println("Compress WORD."); } } public class ToolApplication { public static void main(String[] args) { List<ResourceFile> resourceFiles = listAllResourceFiles(args[0]); Extractor extractor = new Extractor(); for (ResourceFile resourceFile : resourceFiles) { resourceFile.accept(extractor); } Compressor compressor = new Compressor(); for(ResourceFile resourceFile : resourceFiles) { resourceFile.accept(compressor); } } private static List<ResourceFile> listAllResourceFiles(String resourceDirectory) { List<ResourceFile> resourceFiles = new ArrayList<>(); / /... Create different class objects according to the suffix (PDF /PPT /word) by the factory method (PdfFile/PPTFile/WordFile) resourceFiles. Add (new PdfFile("a. df")); resourceFiles.add(new WordFile("b.word")); resourceFiles.add(new PPTFile("c.ppt")); return resourceFiles; }}Copy the code
Visitor pattern in ASM
Above introduced the visitor pattern design original intention and the design method, here again looks at the visitor pattern in the actual project application. The visitor pattern is most commonly used when accessing complex structures or objects, separating data access from data manipulation without changing the data structure, and processing the business logic in the visitor in the form of callbacks. In the face of different access processing, you just need to define a new visitor to implement different access processing logic. This may sound abstract, but you can see in ASM how bytecode files are read and modified using the visitor’s design pattern. ASM uses the ClassReader to traverse the class file structure for class and object information in the file, receives the ClassVisitor in its Accept method, and performs different bytecode operations in the different callback methods of the ClassVisitor. As you can see from the code examples, there are mainly the following classes:
ClassReader cr = new ClassReader(inputStream);
ClassWriter cw = new ClassWriter(cr, 0);
ClassVisitor cv = new InjectCassVisitor(ASM6, cw, methodName);
cr.accept(cv, 0);
return cw.toByteArray();
Copy the code
- ClassReader (Element) : Reads bytecodes of different input types into memory and receives a visit from the ClassVisitor through the Accept method.
- ClassVisitor (Visitor) : It is entirely up to the developer to customize the different types of Visitor, which receives the bytecode information read in the visitXXX callback and processes it accordingly.
conclusion
To understand the design of the visitor pattern, I think the key is to understand the so-called callback after callback. There are two callbacks here, which means there are two types of interfaces. The first callback is for the visitor to accept the visitor, and the second callback is for the visitor to visit the visitor through the visit method. Double dispatch is achieved through two calls that swap types, that is, two single dispatches.
Reference documentation
Wang Zheng < The Beauty of Design Patterns >
www.jianshu.com/p/cd17bae4e… www.liaoxuefeng.com/wiki/125259…