Appearance
访问者模式(Visitor Pattern)笔记
定义
访问者模式是一种行为设计模式,它允许你将算法与对象结构分离,使你能够在不修改现有对象结构的情况下定义新的操作。
英文原文定义:
The Visitor Pattern represents an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
核心概念
访问者模式基于双重分派(double dispatch)原则,通过将操作逻辑从元素类中移出并放入独立的访问者类中,实现了数据结构和操作的分离。
主要角色
- Visitor(访问者接口):为每个ConcreteElement类声明visit操作
- ConcreteVisitor(具体访问者):实现Visitor声明的操作
- Element(元素接口):定义accept方法接受访问者
- ConcreteElement(具体元素):实现accept方法
- ObjectStructure(对象结构):能枚举它的元素,可提供高层接口允许访问者访问元素
优缺点
优点
- 开闭原则:新增操作容易,无需修改元素类
- 单一职责原则:相关行为集中在一个访问者中
- 算法集中:将相关操作集中在一个访问者对象中
- 跨类层次操作:可以跨多个类层次结构进行操作
- 状态累积:访问者可以在遍历过程中累积状态
缺点
- 破坏封装:需要元素类暴露足够多细节给访问者
- 元素接口变更困难:增加新元素类需要修改所有访问者
- 过度设计:对象结构很少变化时才适合使用
- 访问权限:可能需要将元素内部状态公开
解决的问题
访问者模式主要解决以下经典问题:
- 操作与结构分离:需要对复杂对象结构(如组合结构)进行多种不相关操作时
- 避免污染类:当不想让这些操作"污染"元素的类时
- 跨类操作:当操作需要跨越多个类层次结构时
- 累积状态:需要在遍历过程中累积状态信息时
- 工具类膨胀:避免工具类因不断增加的操作而膨胀
实现注意事项
双重分派实现:
- 元素通过accept方法"反向调用"访问者的visit方法
- 这是实现双重分派的关键机制
访问者状态:
- 无状态访问者:可以共享使用(单例)
- 有状态访问者:每次需要新实例
元素接口稳定性:
- 元素类结构应相对稳定
- 频繁添加新元素类会降低模式优势
循环依赖:
- 访问者知道所有元素类
- 元素类知道访问者接口
- 需要谨慎设计避免过度耦合
遍历责任:
- 对象结构可以处理遍历
- 也可以由访问者控制遍历顺序
实现变体
内部访问者:
- 访问者作为元素类的内部类
- 可以访问元素私有成员
默认访问者:
- 提供默认实现的抽象访问者类
- 具体访问者只需覆盖需要的方法
扩展访问者:
- 通过组合多个访问者扩展功能
- 而非创建庞大的访问者类
迭代访问者:
- 将访问者与迭代器结合
- 支持复杂遍历逻辑
与其他模式的关系
相似模式区分
迭代器模式:
- 访问者:对元素执行操作
- 迭代器:遍历集合元素
组合模式:
- 访问者:常用来对组合结构进行操作
- 组合:描述部分-整体层次结构
策略模式:
- 访问者:封装多个操作,可跨类层次
- 策略:封装单个算法
常见搭配组合
- 访问者 + 组合:对组合结构执行操作
- 访问者 + 解释器:在语法树上执行操作
- 访问者 + 迭代器:控制复杂遍历顺序
- 访问者 + 装饰器:动态增强访问者功能
典型应用场景
- 编译器(语法树分析)
- 文档处理(XML/HTML处理)
- 复杂对象结构分析
- 报表生成系统
- 静态代码分析工具
- 3D渲染场景遍历
- 人工智能决策树处理
代码示例(编译器AST实现)
java
// 元素接口
interface ASTNode {
void accept(ASTVisitor visitor);
}
// 具体元素:变量引用
class VariableRefNode implements ASTNode {
private String variableName;
public VariableRefNode(String name) {
this.variableName = name;
}
public String getVariableName() {
return variableName;
}
@Override
public void accept(ASTVisitor visitor) {
visitor.visit(this);
}
}
// 具体元素:赋值语句
class AssignmentNode implements ASTNode {
private VariableRefNode left;
private ASTNode right;
public AssignmentNode(VariableRefNode left, ASTNode right) {
this.left = left;
this.right = right;
}
public VariableRefNode getLeft() {
return left;
}
public ASTNode getRight() {
return right;
}
@Override
public void accept(ASTVisitor visitor) {
visitor.visit(this);
}
}
// 访问者接口
interface ASTVisitor {
void visit(VariableRefNode node);
void visit(AssignmentNode node);
}
// 具体访问者:类型检查
class TypeCheckingVisitor implements ASTVisitor {
@Override
public void visit(VariableRefNode node) {
System.out.println("Type checking variable reference: " + node.getVariableName());
// 实际实现中会检查符号表等
}
@Override
public void visit(AssignmentNode node) {
System.out.println("Type checking assignment");
node.getLeft().accept(this);
node.getRight().accept(this);
// 实际实现中会检查类型兼容性等
}
}
// 具体访问者:代码生成
class CodeGenVisitor implements ASTVisitor {
@Override
public void visit(VariableRefNode node) {
System.out.println("Generating code for variable: " + node.getVariableName());
// 实际实现中会生成加载变量的指令
}
@Override
public void visit(AssignmentNode node) {
System.out.println("Generating assignment code");
node.getRight().accept(this);
node.getLeft().accept(this);
// 实际实现中会生成存储指令
}
}
// 对象结构:AST
class AST implements Iterable<ASTNode> {
private List<ASTNode> nodes = new ArrayList<>();
public void addNode(ASTNode node) {
nodes.add(node);
}
@Override
public Iterator<ASTNode> iterator() {
return nodes.iterator();
}
}
// 客户端代码
public class Compiler {
public static void main(String[] args) {
// 构建简单AST: a = b
AST ast = new AST();
VariableRefNode b = new VariableRefNode("b");
VariableRefNode a = new VariableRefNode("a");
AssignmentNode assign = new AssignmentNode(a, b);
ast.addNode(assign);
// 类型检查
TypeCheckingVisitor typeChecker = new TypeCheckingVisitor();
for (ASTNode node : ast) {
node.accept(typeChecker);
}
// 代码生成
CodeGenVisitor codeGen = new CodeGenVisitor();
for (ASTNode node : ast) {
node.accept(codeGen);
}
}
}