访问者模式是一种行为型设计模式,其核心思想是:将 “数据结构” 与 “数据操作” 解耦,定义一个 “访问者” 对象,封装对数据结构中元素的操作逻辑;数据结构中的元素提供 “接受访问者” 的接口,允许访问者在不修改元素类的前提下,灵活扩展对元素的操作。

该模式的本质是 “分离算法与数据结构”—— 当需要对同一批元素执行多种不同操作(如统计、打印、过滤)时,无需在元素类中添加大量操作方法,只需新增对应的访问者类,符合 “开闭原则”。

访问者模式的UML如下:

image.png

访问者模式的典型应用场景是:

  1. 数据结构稳定,但操作频繁变化
    • 库存系统:商品类型(电子产品、服装)稳定,但需频繁新增操作(统计库存、打印价格、计算折扣);
    • 文档解析:文档元素(标题、段落、图片)稳定,但需新增操作(字数统计、格式检查、内容导出)。
  2. 需要对同一批元素执行多种不相关操作
    • 报表系统:对 “订单”“用户”“商品” 元素,需同时执行 “数据统计”“报表生成”“异常检测” 三种操作,每种操作对应一个访问者。
  3. 操作需要累计状态
    • 财务系统:遍历 “收入”“支出” 元素,通过访问者累计总利润、统计各类支出占比。

下面将用Java代码实现访问者模式,以商品库存系统为场景:定义“电子产品”和“服装”两种商品元素,通过“库存统计访问者”(统计总数量、总价值)和“价格打印访问者”(打印商品详情)实现不同操作,最终通过“库存管理类”(对象结构)统一管理商品并触发访问者操作。

1. 抽象访问者(Visitor)

定义访问所有商品元素的接口,为每种具体商品声明对应的访问方法:

1
2
3
4
5
6
7
// 抽象访问者:定义访问商品的接口
public abstract class Visitor {
// 访问电子产品
public abstract void visitElectronic(Electronic electronic);
// 访问服装
public abstract void visitClothing(Clothing clothing);
}

2. 具体访问者(Concrete Visitor)

实现抽象访问者接口,封装对商品的具体操作逻辑:

(1)库存统计访问者(StockCountVisitor)

统计所有商品的总数量和总价值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 具体访问者1:库存统计(统计总数量、总价值)
public class StockCountVisitor extends Visitor {
private int totalQuantity = 0; // 总数量
private double totalValue = 0.0; // 总价值

// 统计电子产品
@Override
public void visitElectronic(Electronic electronic) {
int quantity = electronic.getQuantity();
double value = electronic.getPrice() * quantity;
totalQuantity += quantity;
totalValue += value;
System.out.println("[统计] 电子产品《" + electronic.getName() + "》:" + quantity + "件,价值" + value + "元");
}

// 统计服装
@Override
public void visitClothing(Clothing clothing) {
int quantity = clothing.getQuantity();
double value = clothing.getPrice() * quantity;
totalQuantity += quantity;
totalValue += value;
System.out.println("[统计] 服装《" + clothing.getName() + "》:" + quantity + "件,价值" + value + "元");
}

// 获取统计结果
public void showTotal() {
System.out.println("=== 库存统计结果 ===");
System.out.println("总商品数量:" + totalQuantity + "件");
System.out.println("总商品价值:" + totalValue + "元");
}
}

(2)价格打印访问者(PricePrintVisitor)

打印所有商品的名称和单价:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 具体访问者2:价格打印(打印商品名称和单价)
public class PricePrintVisitor extends Visitor {
// 打印电子产品价格
@Override
public void visitElectronic(Electronic electronic) {
System.out.println("[价格] 电子产品《" + electronic.getName() + "》:单价" + electronic.getPrice() + "元/件");
}

// 打印服装价格
@Override
public void visitClothing(Clothing clothing) {
System.out.println("[价格] 服装《" + clothing.getName() + "》:单价" + clothing.getPrice() + "元/件");
}
}

3. 抽象元素(Element)

定义商品接受访问者的接口,声明accept方法(核心:让元素主动接受访问者):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 抽象元素:定义商品接受访问者的接口
public abstract class Element {
// 商品名称
protected String name;
// 商品单价
protected double price;
// 商品数量
protected int quantity;

public Element(String name, double price, int quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
}

// 核心方法:接受访问者(让访问者访问自己)
public abstract void accept(Visitor visitor);

// Getter方法(供访问者获取商品属性)
public String getName() {
return name;
}

public double getPrice() {
return price;
}

public int getQuantity() {
return quantity;
}
}

4. 具体元素(Concrete Element)

实现抽象元素接口,在accept方法中显式调用访问者对应的访问方法:

(1)电子产品(Electronic)

1
2
3
4
5
6
7
8
9
10
11
12
// 具体元素1:电子产品
public class Electronic extends Element {
public Electronic(String name, double price, int quantity) {
super(name, price, quantity);
}

// 接受访问者:调用访问者的“访问电子产品”方法
@Override
public void accept(Visitor visitor) {
visitor.visitElectronic(this);
}
}

(2)服装(Clothing)

1
2
3
4
5
6
7
8
9
10
11
12
// 具体元素2:服装
public class Clothing extends Element {
public Clothing(String name, double price, int quantity) {
super(name, price, quantity);
}

// 接受访问者:调用访问者的“访问服装”方法
@Override
public void accept(Visitor visitor) {
visitor.visitClothing(this);
}
}

5. 对象结构(Object Structure)

维护商品集合,提供遍历商品的接口,供访问者批量访问所有元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 对象结构:库存管理(维护商品集合,触发访问者操作)
import java.util.ArrayList;
import java.util.List;

public class Inventory {
// 存储所有商品元素
private List<Element> elements = new ArrayList<>();

// 添加商品到库存
public void addElement(Element element) {
elements.add(element);
}

// 接受访问者:遍历所有商品,让每个商品接受访问者
public void acceptVisitor(Visitor visitor) {
for (Element element : elements) {
element.accept(visitor);
}
}
}

6. 客户端(Client)

创建库存、商品和访问者,触发访问操作,验证访问者模式的工作流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 客户端:测试访问者模式
public class Client {
public static void main(String[] args) {
// 1. 创建对象结构(库存)
Inventory inventory = new Inventory();

// 2. 添加具体元素(商品)
inventory.addElement(new Electronic("智能手机", 3999.0, 50));
inventory.addElement(new Electronic("笔记本电脑", 5999.0, 30));
inventory.addElement(new Clothing("纯棉衬衫", 199.0, 100));
inventory.addElement(new Clothing("休闲裤子", 249.0, 80));

// 3. 创建具体访问者1:库存统计
System.out.println("===== 执行库存统计操作 =====");
StockCountVisitor countVisitor = new StockCountVisitor();
inventory.acceptVisitor(countVisitor);
countVisitor.showTotal(); // 显示统计结果

// 4. 创建具体访问者2:价格打印
System.out.println("\n===== 执行价格打印操作 =====");
PricePrintVisitor printVisitor = new PricePrintVisitor();
inventory.acceptVisitor(printVisitor);
}
}

代码运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
===== 执行库存统计操作 =====
[统计] 电子产品《智能手机》:50件,价值199950.0元
[统计] 电子产品《笔记本电脑》:30件,价值179970.0元
[统计] 服装《纯棉衬衫》:100件,价值19900.0元
[统计] 服装《休闲裤子》:80件,价值19920.0元
=== 库存统计结果 ===
总商品数量:260件
总商品价值:419740.0元

===== 执行价格打印操作 =====
[价格] 电子产品《智能手机》:单价3999.0元/件
[价格] 电子产品《笔记本电脑》:单价5999.0元/件
[价格] 服装《纯棉衬衫》:单价199.0元/件
[价格] 服装《休闲裤子》:单价249.0元/件

访问者模式核心逻辑解析

  1. 解耦操作与结构:商品元素(Electronic/Clothing)仅维护自身属性,统计、打印等操作封装在访问者中,新增操作(如“折扣计算”)只需新增DiscountVisitor,无需修改商品类。
  2. 双重分派机制:通过“元素的accept方法 + 访问者的visit方法”实现——先根据元素类型调用对应accept,再根据访问者类型调用对应visit,确保操作与元素精准匹配。
  3. 对象结构统一调度Inventory作为对象结构,批量管理商品并触发访问者,客户端只需与Inventory和访问者交互,无需关注单个商品的遍历逻辑。