访问者模式 (Visitor Pattern) 深度解析
访问者模式 (Visitor Pattern) 深度解析
🚶 访问者模式 (Visitor Pattern) 深度解析
1. 模式动机与定义
1.1. 模式动机:分离操作与数据结构
在一个对象结构(如复杂的树形或列表)中,包含了很多不同类的对象。我们经常需要对这些对象执行各种不相关的操作(例如:计算成本、生成报表、执行类型检查)。
- 问题:如果将所有操作都放在元素类内部,会导致:
- 元素类职责过多,违反单一职责原则。
- 每增加一个新操作,都需要修改所有元素类,违反开闭原则。
- 操作与数据结构高度耦合,难以扩展。
- 解决方案:访问者模式将作用于数据结构中各元素的操作封装到一个独立的类(访问者)中。
访问者模式可以在不改变数据结构的前提下,定义作用于这些元素的新操作,实现了数据结构与数据操作的分离。
1.2. 模式定义
访问者模式 (Visitor Pattern):
封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新操作。
访问者模式是一种对象行为型模式,主要用于解决稳定的数据结构和易变的操作耦合的问题。
2. 模式结构与角色
访问者模式是少数使用双重分派 (Double Dispatch) 机制来实现的模式。
| 角色名称 | 职责描述 | 对应到计算机组件实例 |
|---|---|---|
| Element (抽象元素) | 定义一个接受访问者的抽象方法(accept(Visitor visitor))。所有具体元素都必须实现此方法。 | ComputerPart 接口 |
| ConcreteElement (具体元素) | 实现了 accept 方法,该方法通常调用访问者的相应 visit 方法,并把自己(this)传递给访问者。 | Mouse, Keyboard, Computer |
| Visitor (抽象访问者) | 为所有具体元素类声明抽象的 visit 方法。每种元素类型都有一个对应的 visit 方法重载。 | ComputerPartVisitor 接口 |
| ConcreteVisitor (具体访问者) | 实现了抽象访问者中声明的每一个 visit 方法,用于实现针对不同元素的不同操作逻辑。 | ComputerPartDisplayVisitor |
2.1. 双重分派机制
访问者模式的关键在于 Element.accept(Visitor) 方法的实现,它体现了双重分派:
- 第一次分派(运行时确定):客户端调用
element.accept(visitor),运行时确定了具体元素 (ConcreteElement) 的类型。 - 第二次分派(运行时确定):在
accept方法内部,调用visitor.visit(this)。由于this是一个具体的元素类型,并且visit方法在Visitor接口中有多个重载,编译器和运行时环境会根据this的实际类型选择调用具体访问者的对应visit方法。
这种机制使得操作(访问者)和数据结构(元素)能够松耦合地配合工作。
3. 代码深度解析(计算机组件展示)
我们以一个计算机组件 (Computer, Mouse, Keyboard) 结构,并对其执行不同的操作(如 Display、CheckHealth)为例。
3.1. Java 代码示例
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// --- 1. 抽象元素 (Element) ---
public interface ComputerPart {
// 接受访问者的方法,将自身引用传入
public void accept(ComputerPartVisitor computerPartVisitor);
}
// --- 2. 具体元素 A: 鼠标 ---
public class Mouse implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
// 双重分派的关键:调用访问者对应 Mouse 类型的 visit 方法
computerPartVisitor.visit(this);
}
}
// --- 3. 具体元素 B: 计算机 (组合结构) ---
public class Computer implements ComputerPart {
ComputerPart[] parts;
public Computer(){
parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()};
}
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
// 遍历所有子元素,让它们都接受访问
for (ComputerPart part : parts) {
part.accept(computerPartVisitor);
}
// 访问者访问 Computer 自身
computerPartVisitor.visit(this);
}
}
// (Keyboard 和 Monitor 类结构类似 Mouse,此处省略)
// --- 4. 抽象访问者 (Visitor) ---
public interface ComputerPartVisitor {
// 为每一个 ConcreteElement 定义一个重载的 visit 方法
public void visit(Computer computer);
public void visit(Mouse mouse);
public void visit(Keyboard keyboard);
// ...
}
// --- 5. 具体访问者 A: 显示组件名称 ---
public class ComputerPartDisplayVisitor implements ComputerPartVisitor {
@Override
public void visit(Computer computer) {
System.out.println("Displaying Computer.");
}
@Override
public void visit(Mouse mouse) {
System.out.println("Displaying Mouse.");
}
@Override
public void visit(Keyboard keyboard) {
System.out.println("Displaying Keyboard.");
}
// ...
}
// --- 6. 客户端调用 (Client) ---
public class VisitorPatternDemo {
public static void main(String[] args) {
ComputerPart computer = new Computer(); // 这是一个组合结构
// 客户端创建具体的访问者并将其传入元素结构
System.out.println("--- 执行 Display 操作 ---");
computer.accept(new ComputerPartDisplayVisitor());
// 如果要新增一个 CheckHealth 操作,只需新增一个 ConcreteVisitor 即可:
// System.out.println("\n--- 执行 CheckHealth 操作 ---");
// computer.accept(new ComputerPartHealthCheckVisitor()); // 无需修改 Computer 或 Part 接口
}
}
3.2. Python 代码示例
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
from abc import ABC, abstractmethod
# --- 1. 抽象访问者 (Visitor) ---
class AssetVisitor(ABC):
@abstractmethod
def visit_stock(self, stock):
pass
@abstractmethod
def visit_bond(self, bond):
pass
# ... 其他资产类型
# --- 2. 抽象元素 (Element) ---
class FinancialAsset(ABC):
def __init__(self, value):
self.value = value
@abstractmethod
def accept(self, visitor: AssetVisitor):
pass
# --- 3. 具体元素 A: 股票 ---
class Stock(FinancialAsset):
def accept(self, visitor: AssetVisitor):
# 第一次分派确定 Stock,第二次分派调用 visit_stock
visitor.visit_stock(self)
# --- 4. 具体元素 B: 债券 ---
class Bond(FinancialAsset):
def accept(self, visitor: AssetVisitor):
visitor.visit_bond(self)
# --- 5. 具体访问者 A: 净值计算器 ---
class NetWorthCalculator(AssetVisitor):
def visit_stock(self, stock):
# 针对股票的特有计算逻辑
print(f"Calculating Stock Net Worth: {stock.value * 1.05:.2f} (5% buffer)")
return stock.value * 1.05
def visit_bond(self, bond):
# 针对债券的特有计算逻辑
print(f"Calculating Bond Net Worth: {bond.value:.2f} (Stable)")
return bond.value
# --- 6. 客户端调用 (Client) ---
if __name__ == "__main__":
portfolio = [
Stock(10000),
Bond(5000),
Stock(2000)
]
# 实例化一个操作:净值计算
calculator = NetWorthCalculator()
total_worth = 0
print("--- Evaluating Portfolio ---")
for asset in portfolio:
# 客户端使用统一的 accept 接口
total_worth += calculator.visit(asset) # Python 动态特性简化了这里的调用,但在类型系统中仍是双重分派
# Python 习惯用法:在 Visitor 中集成调度
# 实际在 Python 中,通常使用 `functools.singledispatch` 或自定义调度
# 这里演示标准的 accept 调用
# 注意:为了简化Python示例,我们通常直接在Visitor类中手动选择方法
# Standard Dispatch (Manual Example):
print("\n--- Standard Visitor Dispatch ---")
for asset in portfolio:
asset.accept(calculator)
4. 模式优点与缺点
4.1. 优点
- 符合单一职责原则:将数据结构和操作分离,操作逻辑封装在访问者中,元素类只负责自身结构和接受访问。
- 优秀的扩展性:增加新的操作(新的
ConcreteVisitor)非常容易,无需修改任何元素类,符合开闭原则。 - 操作集中化:相关的操作逻辑被集中在一个访问者类中,便于管理和维护。
4.2. 缺点
- 具体元素变更困难:如果数据结构本身不稳定(即经常增加新的
ConcreteElement),则需要修改所有Visitor接口和所有ConcreteVisitor类,扩展性极差。 - 违反依赖倒置原则:
AbstractVisitor必须为每一个ConcreteElement定义visit方法,因此它依赖了具体类而非抽象。 - 暴露细节:访问者需要访问元素类的内部状态才能执行操作,具体元素对访问者公布了部分细节,违反了迪米特原则。
5. 适用环境
- 对象结构(Element Classes)中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
- 需要对一个对象结构中的对象进行很多不同并且不相关的操作,且希望避免让这些操作“污染”这些对象的类。
- 一个对象结构包含很多类对象,它们有不同的接口,但你想对它们实施依赖于其具体类型的操作(需要双重分派)。
- 需要对功能进行统一(如报表生成、UI 渲染、拦截器与过滤器)。
本文由作者按照 CC BY 4.0 进行授权