一、模式定义与核心思想
访问者模式 是一种行为设计模式,允许你在不修改现有对象结构的情况下定义新操作。它将算法与对象结构分离,是解决数据结构稳定但操作多变场景的经典方案。
核心思想:通过"双重分派"实现操作与结构的解耦
- 元素通过
accept()方法"接受"访问者 - 访问者通过
visit()方法"访问"元素 - 在运行时确定具体操作
二、Java代码实现
2.1 基础结构实现
arduino
java
java
下载
复制
// 1. 元素接口
public interface DocumentElement {
void accept(DocumentVisitor visitor);
}
// 2. 具体元素:文本节点
public class TextElement implements DocumentElement {
private String content;
public TextElement(String content) {
this.content = content;
}
public String getContent() {
return content;
}
@Override
public void accept(DocumentVisitor visitor) {
visitor.visit(this);
}
}
// 3. 具体元素:图片节点
public class ImageElement implements DocumentElement {
private String src;
private int width;
private int height;
public ImageElement(String src, int width, int height) {
this.src = src;
this.width = width;
this.height = height;
}
public String getSrc() { return src; }
public int getWidth() { return width; }
public int getHeight() { return height; }
@Override
public void accept(DocumentVisitor visitor) {
visitor.visit(this);
}
}
// 4. 访问者接口
public interface DocumentVisitor {
void visit(TextElement text);
void visit(ImageElement image);
}
// 5. 具体访问者:HTML导出
public class HtmlExportVisitor implements DocumentVisitor {
private StringBuilder html = new StringBuilder();
@Override
public void visit(TextElement text) {
html.append("<p>").append(text.getContent()).append("</p>\n");
}
@Override
public void visit(ImageElement image) {
html.append(String.format("<img src="%s" width="%d" height="%d">\n",
image.getSrc(), image.getWidth(), image.getHeight()));
}
public String getHtml() {
return "<!DOCTYPE html>\n<html>\n<body>\n" + html.toString() + "</body>\n</html>";
}
}
// 6. 具体访问者:文档统计
public class StatsVisitor implements DocumentVisitor {
private int textCount = 0;
private int imageCount = 0;
private int totalTextLength = 0;
@Override
public void visit(TextElement text) {
textCount++;
totalTextLength += text.getContent().length();
}
@Override
public void visit(ImageElement image) {
imageCount++;
}
public void printStats() {
System.out.println("文档统计:");
System.out.println("文本段落数:" + textCount);
System.out.println("图片数量:" + imageCount);
System.out.println("总文本长度:" + totalTextLength);
}
}
// 7. 文档结构
public class Document {
private List<DocumentElement> elements = new ArrayList<>();
public void addElement(DocumentElement element) {
elements.add(element);
}
public void accept(DocumentVisitor visitor) {
for (DocumentElement element : elements) {
element.accept(visitor);
}
}
}
// 8. 客户端使用
public class VisitorDemo {
public static void main(String[] args) {
// 构建文档
Document document = new Document();
document.addElement(new TextElement("欢迎阅读本文档"));
document.addElement(new ImageElement("photo.jpg", 800, 600));
document.addElement(new TextElement("这是文档的正文内容"));
// 使用HTML导出访问者
HtmlExportVisitor htmlExporter = new HtmlExportVisitor();
document.accept(htmlExporter);
System.out.println(htmlExporter.getHtml());
// 使用统计访问者
StatsVisitor stats = new StatsVisitor();
document.accept(stats);
stats.printStats();
}
}
2.2 进阶示例:编译器AST处理
typescript
java
java
下载
复制
// AST节点体系
public interface ASTNode {
void accept(ASTVisitor visitor);
}
public class VariableNode implements ASTNode {
private String name;
public VariableNode(String name) { this.name = name; }
public String getName() { return name; }
@Override
public void accept(ASTVisitor visitor) {
visitor.visit(this);
}
}
public class AssignmentNode implements ASTNode {
private VariableNode left;
private ASTNode right;
public AssignmentNode(VariableNode left, ASTNode right) {
this.left = left;
this.right = right;
}
@Override
public void accept(ASTVisitor visitor) {
visitor.visit(this);
left.accept(visitor);
right.accept(visitor);
}
}
// 访问者体系
public interface ASTVisitor {
void visit(VariableNode node);
void visit(AssignmentNode node);
}
// 类型检查访问者
public class TypeCheckVisitor implements ASTVisitor {
@Override
public void visit(VariableNode node) {
System.out.println("类型检查变量: " + node.getName());
}
@Override
public void visit(AssignmentNode node) {
System.out.println("检查赋值类型兼容性");
}
}
// 代码生成访问者
public class CodeGenVisitor implements ASTVisitor {
@Override
public void visit(VariableNode node) {
System.out.println("生成变量引用: " + node.getName());
}
@Override
public void visit(AssignmentNode node) {
System.out.println("生成赋值指令");
}
}
三、应用场景
3.1 典型应用场景
-
编译器与解释器
- 抽象语法树(AST)的多种处理:类型检查、代码优化、代码生成、代码格式化
- 示例:Java编译器、ANTLR、Eclipse JDT
-
文档处理系统
- 多种格式导出:HTML、PDF、Markdown、纯文本
- 文档分析:字数统计、关键词提取、语法检查
-
UI事件处理
- 复杂UI组件树的事件分发
- 示例:Swing中的事件监听器
-
复杂对象结构处理
- 文件系统的多种操作:搜索、复制、权限检查、磁盘使用统计
- XML/JSON文档的多重处理
-
报表生成系统
- 同一数据集的不同报表:PDF报表、Excel报表、图表展示
3.2 使用时机判断
- ✅ 对象结构相对稳定,很少新增元素类型
- ✅ 需要在不修改结构的情况下新增多种操作
- ✅ 相关操作需要访问复杂对象结构的多个不同元素
- ✅ 需要避免"污染"元素类的操作代码
四、优缺点分析
4.1 优点
- 开闭原则:新增操作容易,无需修改现有元素类
- 单一职责:相关操作集中在同一访问者中,提高内聚性
- 算法集中:将相关行为集中在一个类中,便于维护
- 状态累积:访问者可以在遍历过程中累积状态
- 类型安全:编译时检查操作与元素的匹配
4.2 缺点
- 元素扩展困难:新增元素类型需要修改所有访问者接口
- 破坏封装:访问者可能需要公开元素的私有状态
- 依赖具体类:访问者接口依赖具体元素类
- 复杂性增加:对于简单结构,访问者模式可能过度设计
- 难以维护:当元素类型频繁变化时,维护成本高
五、改进与变体
5.1 访问者模式变体
less
java
java
下载
复制
// 变体1:具有默认实现的抽象访问者
public abstract class DefaultDocumentVisitor implements DocumentVisitor {
@Override
public void visit(TextElement text) {
// 默认空实现
}
@Override
public void visit(ImageElement image) {
// 默认空实现
}
}
// 变体2:支持遍历的访问者
public class TraversalVisitor implements DocumentVisitor {
@Override
public void visit(TextElement text) {
// 处理文本
// 如果有子元素,继续遍历
}
@Override
public void visit(ImageElement image) {
// 处理图片
}
}
5.2 结合其他模式
typescript
java
java
下载
复制
// 结合组合模式:处理树形结构
public class CompositeElement implements DocumentElement {
private List<DocumentElement> children = new ArrayList<>();
public void add(DocumentElement element) {
children.add(element);
}
@Override
public void accept(DocumentVisitor visitor) {
for (DocumentElement child : children) {
child.accept(visitor);
}
}
}
// 结合策略模式:动态选择访问者
public class DocumentProcessor {
private DocumentVisitor strategy;
public void setStrategy(DocumentVisitor strategy) {
this.strategy = strategy;
}
public void process(Document document) {
document.accept(strategy);
}
}
六、注意事项与最佳实践
6.1 使用注意事项
-
访问者状态管理
typescriptjava java 下载 复制 // 有状态的访问者示例 public class StatefulVisitor implements DocumentVisitor { private List<String> processedElements = new ArrayList<>(); @Override public void visit(TextElement text) { processedElements.add("Text: " + text.getContent()); // 注意:访问者可能被重用,需要重置状态 } public void reset() { processedElements.clear(); } } -
循环引用处理
typescriptjava java 下载 复制 public class GraphNode implements Visitable { private List<GraphNode> neighbors = new ArrayList<>(); private boolean visited = false; // 防止重复访问 @Override public void accept(Visitor visitor) { if (!visited) { visited = true; visitor.visit(this); for (GraphNode neighbor : neighbors) { neighbor.accept(visitor); } } } } -
性能考虑
- 大量小对象时,虚方法调用开销
- 考虑访问者缓存优化
6.2 最佳实践
-
访问者职责单一化
- 每个访问者只负责一类相关操作
- 避免创建"上帝"访问者
-
元素接口最小化
- 只暴露访问者必需的方法
- 通过参数传递避免暴露内部状态
-
提供默认实现
typescriptjava java 下载 复制 public abstract class AdaptiveVisitor implements DocumentVisitor { // 提供默认实现,子类只需覆盖需要的方法 public void defaultVisit(DocumentElement element) { System.out.println("处理: " + element.getClass().getSimpleName()); } @Override public void visit(TextElement text) { defaultVisit(text); // 特定处理... } } -
使用泛型增强类型安全
arduinojava java 下载 复制 public interface GenericVisitor<T> { T visit(TextElement text); T visit(ImageElement image); }
七、替代方案
-
迭代器模式+策略模式
- 适用于不需要访问具体类型的场景
-
命令模式
- 当操作可以参数化时
-
简单方法重载
- 当元素类型有限且稳定时
-
反射机制
- 牺牲类型安全换取灵活性
- 示例:Apache Commons的BeanUtils
总结
访问者模式是处理"稳定数据结构,多变操作"场景的强大工具,特别适用于编译器、文档处理等需要多种算法处理同一对象结构的系统。其核心价值在于将算法与结构解耦,但代价是增加了元素类型扩展的难度。
关键取舍:在操作易变性和元素稳定性之间取得平衡。当预期会频繁新增操作而很少新增元素类型时,访问者模式是最佳选择;反之,则应考虑其他设计方案。
在实际应用中,应谨慎评估需求变化方向,合理使用访问者模式及其变体,避免过度设计,同时充分利用其提高代码可维护性和扩展性的优势。