Element 具体元素。这里指的是具体被访问的类,在我们这个例子中指的是 Scientist 类。一般情况下,我们会提供一个 accept() 方法,接收访问者参数,将相当于接受其范文申请。但这个方法也不是必须的,只要你能够拿到 visitor 对象,你怎么定义这个参数传递都可以。
文章首发于个人博客 shuyi.tech,欢迎访问更多有趣有价值的文章。对于访问者模式来说,最重要的莫过于 Visitor、ConcreteVisitor、Element 这三个类了。Visitor、ConcreteVisitor 定义访问者具体能做的事情,被访问者的参数通过参数传递给访问者。Element 则通过各种方法拿到被访问者对象,常用的是通过 accept() 方法,但这并不是绝对的。
需要注意的是,我们学习设计模式重点是理解类与类之间的关系,以及他们传递的信息。至于是通过什么方式传递的,是通过 accept() 方法,还是通过构造函数,都不是重点。
02 访问者模式的实际应用前面我们用一个生活的例子帮助大家理解访问者模式,相信大家对访问者模式应该有了个感性的理解了。为了回归编程实践本身,让大家对访问者模式能有更好的实践理解。下面我们将从软件编程上讲讲访问者模式在开源框架中的应用。
文件树遍历JDK 中有文件操作,我们自然是清楚的。有文件操作,那自然就会有文件夹的遍历操作,即访问某个文件夹下面的所有文件或文件夹。试想一下,如果我们想要打印出某个文件夹下所有文件及文件夹的名字,我们需要怎么做?
很简单的做法,其实就是直接做一个树的遍历,然后将名字打印出来呀!
没错,这确实是正确答案!
那么如果我希望统计一下所有文件及文件夹的个数呢?
那就再遍历一次,然后用一个计数器去一直加一呗!
没错,这也是正确答案!
但你是否发现了这两个过程中,我们有一个相同的操作:遍历文件树。无论是打印文件名,还是计算文件树,我们都需要去遍历文件树。而无论哪一个过程,我们最终要的其实就是访问文件。
还记得我们说过设计模式的本质是什么吗?设计模式的本质是找出不变的东西,再找出变化的东西,然后找到合适的数据结构(设计模式)去承载这种变化。
在这个例子里,不变的东西是文件树的遍历,变化的是对于文件的不同访问操作。很显然,访问者模式是比较适合承载这种变化的。我们可以把这种不变的东西(文件树的遍历)固定起来,把变化的东西(文件的具体操作)开放出去。JDK 对于文件树的遍历,其实就是使用访问者模式实现的。
JDK 中声明了一个 FileVisitor 接口,定义了遍历者可以做的操作。
public interface FileVisitor<T> { FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs); FileVisitResult visitFile(T file, BasicFileAttributes attrs) throws IOException; FileVisitResult visitFileFailed(T file, IOException exc) throws IOException; FileVisitResult postVisitDirectory(T dir, IOException exc) throws IOException; }FileVisitor 中定义的 visitFile() 方法,其实就是对于文件的访问。被访问者(文件)的信息通过第一个参数 file 传递过来。这样遍历者就可以访问文件的内容了。
SimpleFileVisitor 则是对于 FileVisitor 接口的实现,该类中仅仅是做了简单的参数校验,并没有太过的逻辑。
public class SimpleFileVisitor<T> implements FileVisitor<T> { @Override public FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs) throws IOException { Objects.requireNonNull(dir); Objects.requireNonNull(attrs); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(T file, BasicFileAttributes attrs) throws IOException { Objects.requireNonNull(file); Objects.requireNonNull(attrs); return FileVisitResult.CONTINUE; } //....其他省略 }FileVisitor 类和 SimpleFileVisitor 类对应的就是 UML 类图中的 Visitor 和 ConcreteVisitor 类。而 Element 元素,对应的其实是 JDK 中的 Files 类。
Files 文件中遍历文件树是通过 walkFileTree() 方法实现的。在 walkFileTree() 方法中实现了树的遍历,在遍历到文件的时候会通过 visitor 类的 visitFile 方法调用遍历者的方法,将遍历到的文件传递给遍历者,从而达到分离变化的目的。
ASM修改字节码ASM 是 Java 的字节码增强技术,这里面就用到了访问者模式,主要是用来进行字节码的修改。在 ASM 中于此相关的三个类分别是:ClassReader、ClassVisitor、ClassWriter。