访问者模式
1. 概念
访问者模式是一种操作一组对象的操作,它的目的是可以在不改变各元素的类的前提下,允许新增不同的访问者,从而来定义对这些元素的新的操作。
访问者模式可以将数据结构和对数据结构的操作解耦,使得增加对数据结构的操作不需要去修改数据结构,也不必去修改原有的操作,再执行时再定义新的Visitor实现者就行了。
访问者模式的设计比较复杂,其一般有抽象访问者、具体访问者、抽象节点元素、具体节点元素、结构对象和客户端几种角色,其具体作用为:

- 抽象访问者(Visitor)——声明所有访问者需要的接口
- 具体访问者(ConcreteVisitor)——实现抽象访问者声明的接口
- 抽象节点元素(Element)——提供一个接口,能够接受访问者作为参数传递给节点元素
- 具体节点元素(ConcreteElement)——实现抽象节点元素声明的接口
- 结构对象(ObjectStructure)——提供一个接口,能够访问到所有的节点元素,一般作为一个集合特有节点元素的引用
- 客户端(Client)——分别创建访问者和节点元素的对象,调用访问者访问变量节点元素
2. 示例
我们将创建一个定义接受操作的 ComputerPart
接口。 Keyboard
、 Mouse
、 Monitor
和 Computer
是实现了 ComputerPart
接口的实体类。我们将定义另一个接口 ComputerPartVisitor
,它定义了访问者类的操作。Computer
使用实体访问者来执行相应的动作。
最后,我们在客户端类 VisitorPatternDemo
中使用 Computer
、 ComputerPartVisitor
类来演示访问者模式的用法。

定义一个表示元素的接口
public interface ComputerPart { void accept(ComputerPartVisitor computerPartVisitor); }
创建扩展了上述类的实体类
public class Keyboard implements ComputerPart { @Override public void accept(ComputerPartVisitor computerPartVisitor) { computerPartVisitor.visit(this); } }
public class Monitor implements ComputerPart { @Override public void accept(ComputerPartVisitor computerPartVisitor) { computerPartVisitor.visit(this); } }
public class Mouse implements ComputerPart { @Override public void accept(ComputerPartVisitor computerPartVisitor) { computerPartVisitor.visit(this); } }
public class Computer implements ComputerPart { ComputerPart[] parts; public Computer(){ parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()}; } @Override public void accept(ComputerPartVisitor computerPartVisitor) { for (int i = 0; i < parts.length; i++) { parts[i].accept(computerPartVisitor); } computerPartVisitor.visit(this); } }
定义一个表示访问者的接口
public interface ComputerPartVisitor { void visit(Computer computer); void visit(Mouse mouse); void visit(Keyboard keyboard); void visit(Monitor monitor); }
创建实现了上述类的实体访问者
public class ComputerPartDisplayVisitor implements ComputerPartVisitor { @Override public void visit(Computer computer) { System.out.println("Displaying Computer."); } @Override public void visit(Mouse mouse) { System.out.println("Displaying Mouse."); } @Override public void visit(Keyboard keyboard) { System.out.println("Displaying Keyboard."); } @Override public void visit(Monitor monitor) { System.out.println("Displaying Monitor."); } }
使用
ComputerPartDisplayVisitor
来显示Computer
的组成部分public class VisitorPatternDemo { public static void main(String[] args) { ComputerPart computer = new Computer(); computer.accept(new ComputerPartDisplayVisitor()); } }
执行程序,输出结果: Displaying Mouse. Displaying Keyboard. Displaying Monitor. Displaying Computer.
Javac中的访问者模式
Javac中的访问者模式——在Javac中,不同的编译阶段都定义了不同的访问者模式实现:

Vistor
无疑是作为抽象访问者角色的,而TreeScanner
, Enter
, Attr
, Gen
, Flow
等都是作为具体访问者角色的,每个访问者角色都定义了自己的访问规则。而Tree接口就是抽象节点元素,JCIf
, JCTry
, JCBreak
, JCReturn
等都是作为具体节点元素的,它们作为一个稳定的数据结构存在。其中的JCCompilationUnit
作为结构对象持有整个语法树,而JavaCompiler
就是Client
了。它同时持有ObjectStructure
和ConcreteElement
对象,即可以使访问者访问节点元素。
下面再看看访问者是如何访问节点元素的,为什么不同的访问者可以访问同一套数据结构,而访问者的行为却可以各自不同,下图是访问者变量语法树的时序图:

Enter
作为一个访问者,实现了抽象访问者Visitor
中的所有接口,但是Enter
这个访问者具体要实现什么功能由Enter
自己定义。遍历这棵树使用统一接口accept
,每个节点元素都实现这具accept
接口,但是不同节点元素的accept
的实现是不同的,以区分不同的节点元素对应不同的功能。