设计模式之访问者模式
# 设计模式之访问者模式
# 一、简介
将数据的结构与数据的操作行为分离,即:把不变的固定起来,变化的开放出去
# 二、实现方式
抽象访问者(Visitor): 访问者的抽象类或接口,定义了访问者的行为
public interface Visitor { public void askSchoolExperience(String name); public void askWorkExperience(String name); public void askScienceAchievement(String name); }
1
2
3
4
5具体访问者(ConcreteVisitor): 实现了
Visitor
public class XinhuaVisitor implements Visitor{ @Override public void askSchoolExperience(String name) { System.out.printf("请问%s:在学校取得的最大成就是什么?\n", name); } @Override public void askWorkExperience(String name) { System.out.printf("请问%s:工作上最难忘的事情是什么?\n", name); } @Override public void askScienceAchievement(String name) { System.out.printf("请问%s:最大的科研成果是什么?", name); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14ObjectStructure:能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问元素
抽象元素(Element):定义一个
accept
方法,用来接收一个访问者对象具体元素(ConcreteElement):实现了
Element
,重写了accept
方法public class Scientist { private Visitor visitor; private String name; private Scientist(){} public Scientist(String name) { this.name = name; } public void accept(Visitor visitor) { this.visitor = visitor; } public void interview(){ System.out.println("------------访问开始------------"); System.out.println("---开始聊学校经历---"); visitor.askSchoolExperience(name); System.out.println("---开始聊工作经历---"); visitor.askWorkExperience(name); System.out.println("---开始聊科研成果---"); visitor.askScienceAchievement(name); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20测试类
public class Client { public static void main(String[] args) { Scientist yang = new Scientist("杨振宁"); yang.accept(new XinhuaVisitor()); yang.interview(); } } ------------访问开始------------ ---开始聊学校经历--- 请问杨振宁:在学校取得的最大成就是什么? ---开始聊工作经历--- 请问杨振宁:工作上最难忘的意见事情是什么? ---开始聊科研成果--- 请问杨振宁:最大的科研成果是什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 三、应用场景
对象结构相对稳定,但其操作算法经常变化的场景
# 1、访问者模式在 ASM 框架中的使用
ASM
是 Java 的字节码增强技术,可以用来对字节码进行修改。在 ASM 中和访问者模式相关的三个类是:ClassVisitor
、ClassWriter
、ClassReader
ClassVisitor
是一个抽象类,定义了访问者的行为。相当于抽象访问者ClassWriter
实现了ClassVisitor
,相当于具体访问者它负责将修改后的字节码输出为字节数组
public final void visitSource(String var1, String var2) { if (var1 != null) { this.sourceFile = this.newUTF8(var1); } if (var2 != null) { this.sourceDebug = (new ByteVector()).encodeUTF8(var2, 0, 2147483647); } } public final void visitOuterClass(String var1, String var2, String var3) { this.enclosingMethodOwner = this.newClass(var1); if (var2 != null && var3 != null) { this.enclosingMethod = this.newNameType(var2, var3); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18ClassReader
类的accept ()
方法,传入了一个ClassVisitor
对象。相当于具体元素它负责将字节数组或 class 文件读入内存中,并以树的数据结构表示
public void accept(ClassVisitor var1, Attribute[] var2, int var3) {
...
...
var1.visit(this.readInt(this.items[1] - 7), var7, var8, var28, var9, var10);
if ((var3 & 2) == 0 && (var12 != null || var13 != null)) {
var1.visitSource(var12, var13);
}
if (var14 != null) {
var1.visitOuterClass(var14, var15, var16);
}
int var29;
if (var17 != 0) {
var23 = this.readUnsignedShort(var17);
for(var29 = var17 + 2; var23 > 0; --var23) {
var29 = this.readAnnotationValues(var29 + 2, var5, true, var1.visitAnnotation(this.readUTF8(var29, var5), true));
}
}
...
...
var1.visitEnd();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 2、访问者模式在 JDK 文件树遍历的使用
假如说我们现在需要实现这样一个功能,循环打印出文件和文件夹的名称,又或者统计文件和文件夹的大小。
在这里,不变的是文件的遍历操作,变化的是打印文件名称和统计文件大小的操作。
JDK 中声明了一个
FileVisitor
接口,定义了遍历者可以做的操作,相当于抽象访问者FileVisitor
中定义的visitFile ()
方法,其实就是对于文件的访问。被访问者(文件)的信息通过第一个参数 file 传递过来。这样遍历者就可以访问文件的内容了
SimpleFileVisitor
实现了FileVisitor
接口,相当于具体访问者JDK 中的
Files
类,相当于具体元素`Files
类中的walkFileTree ()
方法实现了文件树的遍历,通过FileVisitor
类的visitFile()
等方法将遍历到的文件传递给遍历者,从而达到分离变化的目的