文章目录
- 0.个人感悟
- 1.概念
- 2.适配场景
-
- [2.1 适合的场景](#2.1 适合的场景)
- 2.2常见场景举例
- 3.实现方法
-
- [3.1 实现思路](#3.1 实现思路)
- [3.2 UML类图](#3.2 UML类图)
- [3.3 代码示例](#3.3 代码示例)
- 4.优缺点
-
- [4.1 优点](#4.1 优点)
- [4.2 缺点](#4.2 缺点)
0.个人感悟
- 访问者,顾名思义,只想访问数据,不想负责(对原数据结构产生破坏)
- 想要访问,需要访问者和元素(被访问者)双方都同意,所以访问者要指定访问各个元素的操作,元素要留后门给访问者(提供方法)。所以初看访问者模式觉得很别扭,相互绑定
- 可以看到,访问者模式扩展起来很麻烦,访问者依赖具体的元素。如果提供新的元素意味着所有访问者都要提供访问新元素的方法。所有建议数据结构稳定的业务来使用
1.概念
英文定义 (《设计模式:可复用面向对象软件的基础》)
Represent 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),即操作的选择取决于两个因素:访问者的类型和元素的类型。从类图也能看出来它们相互关联
2.适配场景
2.1 适合的场景
- 操作频繁变化:经常需要添加新的操作,但对象结构相对稳定
- 避免污染元素类:不想在每个元素类中添加大量的操作方法
2.2常见场景举例
- 文档处理:对文档中的不同元素(段落、图片、表格)进行导出、格式化、统计等操作
- 电商系统:购物车中不同商品的计价、折扣、税计算
- 游戏开发:游戏对象的不同行为检测、状态更新、渲染
3.实现方法
3.1 实现思路
- 定义访问者接口:为每个具体元素类声明一个visit方法
- 创建具体访问者类:实现访问者接口,定义对每种元素的具体操作
- 定义元素接口:声明accept方法,接受访问者作为参数
- 实现具体元素类:实现accept方法,调用访问者的对应visit方法
- 构建对象结构:可以是一个集合、组合结构等,提供遍历方法
3.2 UML类图

角色说明:
- Visitor(访问者):为每个ConcreteElement声明一个visit操作
- ConcreteVisitor(具体访问者):实现Visitor声明的操作
- Element(元素):定义accept操作,以Visitor作为参数
- ConcreteElement(具体元素):实现accept操作
- ObjectStructure(对象结构):能枚举它的元素,可提供高层接口让Visitor访问它的元素
3.3 代码示例
背景:购物车中有很多商品,对于这些商品会有打印明细、总价、折扣计算等访问。
这里简化购物数量
元素接口和访问者接口:
java
public interface ShoppingItem {
/**
* @param visitor 访问者
* @description 接受访问者
* @author bigHao
* @date 2026/1/20
**/
void accept(CartVisitor visitor);
/**
* @return double 单价
* @description 获取单价
* @author bigHao
* @date 2026/1/20
**/
double getPrice();
/**
* @return java.lang.String 名称
* @description 获取名称
* @author bigHao
* @date 2026/1/20
**/
String getName();
}
public interface CartVisitor {
/**
* @param apple 苹果
* @description 访问苹果
* @author bigHao
* @date 2026/1/20
**/
void visit(Apple apple);
/**
* @param cola 可乐
* @description 访问可乐
* @author bigHao
* @date 2026/1/20
**/
void visit(Cola cola);
}
元素具体实现类:
java
public class Apple implements ShoppingItem{
@Override
public void accept(CartVisitor visitor) {
visitor.visit(this);
}
@Override
public double getPrice() {
return 5.0;
}
@Override
public String getName() {
return "Apple";
}
}
public class Cola implements ShoppingItem {
@Override
public void accept(CartVisitor visitor) {
visitor.visit(this);
}
@Override
public double getPrice() {
return 2.5;
}
@Override
public String getName() {
return "Cola";
}
}
具体访问者:
java
public class DetailsVisitor implements CartVisitor {
public static final String SPLIT = ";";
private String info = "";
@Override
public void visit(Apple apple) {
info += apple.getName() + " " + apple.getPrice() + SPLIT;
}
@Override
public void visit(Cola cola) {
info += cola.getName() + " " + cola.getPrice() + SPLIT;
}
public String getInfo() {
return info;
}
}
public class TotalPriceVisitor implements CartVisitor {
private double totalPrice;
@Override
public void visit(Apple apple) {
double price = apple.getPrice();
totalPrice += price;
}
@Override
public void visit(Cola cola) {
double price = cola.getPrice();
totalPrice += price;
}
public double getTotalPrice() {
return totalPrice;
}
}
对象结构,购物车:
java
public class Cart {
private List<ShoppingItem> shoppingItems = new ArrayList<ShoppingItem>(20);
public void addItem(ShoppingItem shoppingItem) {
shoppingItems.add(shoppingItem);
}
public void removeItem(ShoppingItem shoppingItem) {
shoppingItems.remove(shoppingItem);
}
public void accept(CartVisitor visitor) {
shoppingItems.forEach(item -> item.accept(visitor));
}
}
测试:
java
public class Client {
static void main() {
// 购物
Cart cart = new Cart();
Apple apple = new Apple();
Cola cola = new Cola();
cart.addItem(apple);
cart.addItem(cola);
// 明细
DetailsVisitor detailVisitor = new DetailsVisitor();
cart.accept(detailVisitor);
System.out.println("购物清单: " + detailVisitor.getInfo());
// 总价
TotalPriceVisitor totalPriceVisitor = new TotalPriceVisitor();
cart.accept(totalPriceVisitor);
System.out.println("总价: " + totalPriceVisitor.getTotalPrice());
}
}
输出:
购物清单: Apple 5.0;Cola 2.5;
总价: 7.5
4.优缺点
4.1 优点
- 开闭原则:易于增加新的操作,无需修改现有元素类
- 单一职责原则:将相关操作集中在一个访问者类中
- 高内聚低耦合:操作与数据结构分离,降低耦合度
- 复用性:可以在不同上下文中复用访问者
- 可维护性:新增操作只需添加新的访问者类
4.2 缺点
- 违反开闭原则(元素方面):增加新的元素类型困难,需要修改所有访问者接口和实现
- 破坏封装:访问者可能需要访问元素的私有成员
- 依赖具体类:访问者方法基于具体元素类,而非抽象
- 对象结构变化受限:元素接口的accept方法必须稳定
- 复杂性增加:对于简单操作,使用访问者模式可能过于复杂
参考: