访问者模式
访问者模式:分离算法与数据结构的设计智慧
一、访问者模式的核心定义
访问者模式(Visitor Pattern) 是一种行为型设计模式,其核心思想是 分离数据结构与作用于数据结构上的操作。当需要对一个复杂对象结构中的所有元素执行多种不同操作,且操作频繁变化时,访问者模式可让操作独立于元素类而变化,避免修改元素类代码,同时简化新操作的添加。
简单来说:访问者模式就像 “上门服务”—— 数据结构(如对象集合)是 “住户”,访问者(操作逻辑)是 “服务人员”,服务人员可根据住户类型提供不同服务,且新增服务无需改动住户本身。
二、访问者模式的核心结构
访问者模式包含 5 个关键角色,各角色职责明确、协同工作:
| 角色 | 核心职责 |
|---|---|
| 抽象访问者(Visitor) | 定义访问所有具体元素的接口,声明与元素类对应的 visit() 方法(一个元素对应一个方法)。 |
| 具体访问者(ConcreteVisitor) | 实现抽象访问者的接口,封装对具体元素的特定操作逻辑(如统计、打印、计算等)。 |
| 抽象元素(Element) | 定义接收访问者的接口,声明 accept(Visitor visitor) 方法,该方法会将自身传入访问者。 |
| 具体元素(ConcreteElement) | 实现抽象元素的 accept() 方法,调用访问者对应的 visit() 方法,完成 “双重分派”。 |
| 对象结构(ObjectStructure) | 存储元素集合,提供遍历元素的方法,可主动邀请访问者访问所有元素(可选角色)。 |
核心逻辑:双重分派
访问者模式的关键是 “双重分派” 机制,即:
-
元素的
accept()方法接收访问者,调用访问者的visit(this)(第一重分派:确定元素类型); -
访问者的
visit()方法根据传入的元素实例,执行对应操作(第二重分派:确定操作类型)。
这种机制确保了 “操作与元素” 的动态绑定,无需修改元素类即可扩展新操作。
三、访问者模式的代码实现
以 “文档编辑器” 为例:文档包含文本(Text)、图片(Image)、表格(Table)三种元素,需实现 “统计元素数量” 和 “导出为 PDF” 两种操作,用访问者模式分离元素与操作。
1. 抽象元素(Element)
// 抽象元素:定义接收访问者的方法
public interface Element {
void accept(Visitor visitor);
}
2. 具体元素(ConcreteElement)
// 具体元素:文本
public class Text implements Element {
private String content;
public Text(String content) {
this.content = content;
}
public String getContent() {
return content;
}
@Override
public void accept(Visitor visitor) {
// 调用访问者对应自身的方法
visitor.visit(this);
}
}
// 具体元素:图片
public class Image implements Element {
private String name;
private int size; // 图片大小(KB)
public Image(String name, int size) {
this.name = name;
this.size = size;
}
public String getName() {
return name;
}
public int getSize() {
return size;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
// 具体元素:表格
public class Table implements Element {
private int row;
private int col;
public Table(int row, int col) {
this.row = row;
this.col = col;
}
public int getRow() {
return row;
}
public int getCol() {
return col;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
3. 抽象访问者(Visitor)
// 抽象访问者:声明访问所有具体元素的方法
public interface Visitor {
void visit(Text text); // 访问文本元素
void visit(Image image); // 访问图片元素
void visit(Table table); // 访问表格元素
}
4. 具体访问者(ConcreteVisitor)
// 具体访问者1:统计元素数量
public class CountVisitor implements Visitor {
private int textCount = 0;
private int imageCount = 0;
private int tableCount = 0;
@Override
public void visit(Text text) {
textCount++;
System.out.println("统计文本:" + text.getContent().length() + "...");
}
@Override
public void visit(Image image) {
imageCount++;
System.out.println("统计图片:" + image.getName() + "(大小:" + image.getSize() + "KB)");
}
@Override
public void visit(Table table) {
tableCount++;
System.out.println("统计表格:" + table.getRow() + "行" + table.getCol() + "列");
}
// 输出统计结果
public void printResult() {
System.out.println("\n=== 元素统计结果 ===");
System.out.println("文本数量:" + textCount);
System.out.println("图片数量:" + imageCount);
System.out.println("表格数量:" + tableCount);
System.out.println("总元素数:" + (textCount + imageCount + tableCount));
}
}
// 具体访问者2:导出为PDF
public class ExportPdfVisitor implements Visitor {
@Override
public void visit(Text text) {
System.out.println("导出文本到PDF:" + text.getContent());
}
@Override
public void visit(Image image) {
System.out.println("导出图片到PDF:" + image.getName() + "(压缩至" + image.getSize() * 0.8 + "KB)");
}
@Override
public void visit(Table table) {
System.out.println("导出表格到PDF:" + table.getRow() + "行" + table.getCol() + "列(格式化布局)");
}
}
5. 对象结构(ObjectStructure)
// 对象结构:管理元素集合,提供遍历功能
import java.util.ArrayList;
import java.util.List;
public class Document {
private List<Element> elements = new ArrayList<>();
// 添加元素
public void addElement(Element element) {
elements.add(element);
}
// 移除元素
public void removeElement(Element element) {
elements.remove(element);
}
// 接受访问者,遍历所有元素并让访问者访问
public void accept(Visitor visitor) {
for (Element element : elements) {
element.accept(visitor);
}
}
}
6. 客户端调用
public class Client {
public static void main(String[] args) {
// 1. 创建文档(对象结构)并添加元素
Document document = new Document();
document.addElement(new Text("访问者模式详解"));
document.addElement(new Image("设计模式示意图", 200));
document.addElement(new Table(5, 3));
document.addElement(new Text("双重分派机制"));
document.addElement(new Image("代码示例截图", 150));
// 2. 统计元素数量(访问者1)
CountVisitor countVisitor = new CountVisitor();
document.accept(countVisitor);
countVisitor.printResult();
// 3. 导出为PDF(访问者2)
System.out.println("\n=== 开始导出PDF ===");
ExportPdfVisitor exportVisitor = new ExportPdfVisitor();
document.accept(exportVisitor);
}
}
输出结果
统计文本:访问者模式详解...
统计图片:设计模式示意图(大小:200KB)
统计表格:5行3列
统计文本:双重分派机制...
统计图片:代码示例截图(大小:150KB)
\=== 元素统计结果 ===
文本数量:2
图片数量:2
表格数量:1
总元素数:5
\=== 开始导出PDF ===
导出文本到PDF:访问者模式详解
导出图片到PDF:设计模式示意图(压缩至160.0KB)
导出表格到PDF:5行3列(格式化布局)
导出文本到PDF:双重分派机制
导出图片到PDF:代码示例截图(压缩至120.0KB)
四、访问者模式的适用场景
当满足以下条件时,适合使用访问者模式:
-
数据结构稳定,但操作频繁变化:如示例中的 “文档元素”(文本、图片、表格)很少新增,但 “统计、导出、校验” 等操作可能不断扩展;
-
需要对复杂对象结构中的所有元素执行统一操作:如遍历集合中的不同类型对象,执行相同逻辑的变体(如统计、格式化);
-
避免在元素类中添加过多操作方法:若操作逻辑分散在各个元素类中,会导致类膨胀、耦合度高,访问者模式可将操作集中管理。
五、访问者模式的优势与缺点
优势
-
符合开闭原则:新增操作只需添加新的具体访问者,无需修改元素类和对象结构,扩展灵活;
-
分离关注点:将数据结构与操作逻辑分离,元素类只关注自身数据,操作逻辑集中在访问者中,代码结构清晰;
-
便于批量处理:通过对象结构的遍历功能,可快速对所有元素执行同一访问者的操作,提高代码复用性。
缺点
-
元素类变更困难:若新增或修改元素类(如文档新增 “视频” 元素),需修改抽象访问者和所有具体访问者的接口,违反开闭原则;
-
破坏封装性:访问者需要访问元素的内部状态(如图片的大小、表格的行列数),可能会暴露元素的私有属性,降低封装性;
-
复杂度较高:双重分派机制和多角色协作增加了代码理解成本,适合复杂场景,简单场景可能显得冗余。
六、访问者模式的实际应用场景
-
框架源码:Java 的
java.nio.file.FileVisitor(文件访问器)、Spring 的BeanDefinitionVisitor(Bean 定义遍历); -
数据处理工具:Excel 导出工具(不同单元格类型(文本、数字、公式)的导出逻辑)、JSON 解析器(不同数据类型的解析逻辑);
-
业务系统:电商订单结算(不同商品类型的折扣计算)、报表生成(不同数据维度的统计与展示)。

浙公网安备 33010602011771号