访问者模式详解
一、模式定义
访问者模式(Visitor Pattern)表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。
二、核心结构
1. 抽象元素接口
public interface Element {
void accept(Visitor visitor);
}
2. 具体元素类
public class ConcreteElementA implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationA() {
return "具体元素A的操作";
}
}
public class ConcreteElementB implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationB() {
return "具体元素B的操作";
}
}
3. 抽象访问者接口
public interface Visitor {
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
}
4. 具体访问者实现
public class ConcreteVisitor1 implements Visitor {
public void visit(ConcreteElementA element) {
System.out.println("访问者1访问" + element.operationA());
}
public void visit(ConcreteElementB element) {
System.out.println("访问者1访问" + element.operationB());
}
}
public class ConcreteVisitor2 implements Visitor {
public void visit(ConcreteElementA element) {
System.out.println("访问者2访问" + element.operationA());
}
public void visit(ConcreteElementB element) {
System.out.println("访问者2访问" + element.operationB());
}
}
5. 对象结构类
public class ObjectStructure {
private List<Element> elements = new ArrayList<>();
public void attach(Element element) {
elements.add(element);
}
public void detach(Element element) {
elements.remove(element);
}
public void accept(Visitor visitor) {
for (Element element : elements) {
element.accept(visitor);
}
}
}
三、完整示例:编译器系统
1. AST节点定义
public interface ASTNode extends Element {
String getNodeName();
}
public class VariableNode implements ASTNode {
private String name;
public VariableNode(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String getNodeName() {
return "Variable:" + name;
}
}
public class AssignmentNode implements ASTNode {
private ASTNode left;
private ASTNode right;
public AssignmentNode(ASTNode left, ASTNode right) {
this.left = left;
this.right = right;
}
public ASTNode getLeft() {
return left;
}
public ASTNode getRight() {
return right;
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String getNodeName() {
return "Assignment";
}
}
2. 访问者接口
public interface ASTVisitor extends Visitor {
void visit(VariableNode node);
void visit(AssignmentNode node);
}
3. 具体访问者
// 类型检查访问者
public class TypeCheckVisitor implements ASTVisitor {
public void visit(VariableNode node) {
System.out.println("类型检查变量: " + node.getName());
}
public void visit(AssignmentNode node) {
System.out.println("检查赋值语句类型兼容性");
node.getLeft().accept(this);
node.getRight().accept(this);
}
}
// 代码生成访问者
public class CodeGenVisitor implements ASTVisitor {
public void visit(VariableNode node) {
System.out.println("生成变量代码: " + node.getName());
}
public void visit(AssignmentNode node) {
System.out.println("生成赋值代码");
node.getLeft().accept(this);
node.getRight().accept(this);
}
}
4. 客户端使用
public class CompilerClient {
public static void main(String[] args) {
ASTNode ast = new AssignmentNode(
new VariableNode("x"),
new VariableNode("y")
);
ObjectStructure structure = new ObjectStructure();
structure.attach(ast);
// 类型检查
structure.accept(new TypeCheckVisitor());
// 代码生成
structure.accept(new CodeGenVisitor());
}
}
四、高级应用:文件系统访问
1. 文件系统元素
public interface FileSystemElement extends Element {
String getName();
long getSize();
}
public class File implements FileSystemElement {
private String name;
private long size;
public File(String name, long size) {
this.name = name;
this.size = size;
}
public String getName() {
return name;
}
public long getSize() {
return size;
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class Directory implements FileSystemElement {
private String name;
private List<FileSystemElement> children = new ArrayList<>();
public Directory(String name) {
this.name = name;
}
public void add(FileSystemElement element) {
children.add(element);
}
public String getName() {
return name;
}
public long getSize() {
long total = 0;
for (FileSystemElement child : children) {
total += child.getSize();
}
return total;
}
public void accept(Visitor visitor) {
visitor.visit(this);
for (FileSystemElement child : children) {
child.accept(visitor);
}
}
}
2. 文件系统访问者
public interface FileSystemVisitor extends Visitor {
void visit(File file);
void visit(Directory directory);
}
// 大小计算访问者
public class SizeCalculatorVisitor implements FileSystemVisitor {
private long totalSize = 0;
public void visit(File file) {
totalSize += file.getSize();
}
public void visit(Directory directory) {
// 目录本身不占空间
}
public long getTotalSize() {
return totalSize;
}
}
// 文件查找访问者
public class FileSearchVisitor implements FileSystemVisitor {
private String searchName;
private List<File> results = new ArrayList<>();
public FileSearchVisitor(String searchName) {
this.searchName = searchName;
}
public void visit(File file) {
if (file.getName().contains(searchName)) {
results.add(file);
}
}
public void visit(Directory directory) {
// 不处理目录
}
public List<File> getResults() {
return results;
}
}
五、模式优势
- 增加新操作容易
- 将相关操作集中在一个访问者中
- 访问者可以跨多个类层次结构操作
- 符合
单一职责原则
和开闭原则
六、适用场景
- 对象结构包含多个类,且需要对这些类进行不同操作
- 需要对对象结构中的类进行不相关的操作
- 对象结构很少变化但经常需要新增操作
- 需要跨多个类层次结构的操作
七、注意事项
- 破坏封装性,要求元素公开内部状态
- 增加新的元素类困难
- 对象结构变化会导致所有访问者修改
- 可能违反
依赖倒置原则
八、最佳实践
- 为对象结构提供稳定的接口
- 使用访问者组合模式处理复杂结构
- 考虑使用反射减少访问者接口方法
- 为常用操作提供默认访问者实现
- 合理设计访问者与被访问元素的交互方式
九、完整示例代码结构
java
src/
├── main/
│ ├── java/
│ │ ├── visitor/
│ │ │ ├── Element.java
│ │ │ ├── ConcreteElementA.java
│ │ │ ├── ConcreteElementB.java
│ │ │ ├── Visitor.java
│ │ │ ├── ConcreteVisitor1.java
│ │ │ ├── ConcreteVisitor2.java
│ │ │ ├── ObjectStructure.java
│ │ │ ├── ASTNode.java
│ │ │ ├── VariableNode.java
│ │ │ ├── AssignmentNode.java
│ │ │ ├── ASTVisitor.java
│ │ │ ├── TypeCheckVisitor.java
│ │ │ ├── CodeGenVisitor.java
│ │ │ ├── CompilerClient.java
│ │ │ ├── FileSystemElement.java
│ │ │ ├── File.java
│ │ │ ├── Directory.java
│ │ │ ├── FileSystemVisitor.java
│ │ │ ├── SizeCalculatorVisitor.java
│ │ │ └── FileSearchVisitor.java