📋 Research Summary
访问者模式是处理复杂对象结构操作的进阶模式。在编译器 AST 遍历、DOM 操作、报表生成中应用。它通过"双分派"机制,将操作与对象结构分离,但代价是增加了系统的复杂性。
🌱 逻辑原点
如果有一个复杂的图形结构(点、线、圆、矩形),你需要支持导出 XML、导出 JSON、计算面积、渲染四种操作,而且未来可能增加更多操作,是为每个图形类添加 4 个方法,还是找到一种方式让操作独立于图形类演化?
操作扩展与类修改的矛盾:操作经常变化,但为每个类添加新方法违反了开闭原则。

🧠 苏格拉底式对话
1️⃣ 现状:最原始的解法是什么?
在每个图形类中添加操作方法:
python
class Circle:
def to_xml(self): ...
def to_json(self): ...
def area(self): ...
def render(self): ...
class Rectangle:
def to_xml(self): ...
def to_json(self): ...
def area(self): ...
def render(self): ...
# 每个新图形都要实现 4 个方法
优点 :直观,操作与数据在一起。
问题:新增操作需要修改所有图形类,违反开闭原则。
2️⃣ 瓶颈:规模扩大 100 倍时会在哪里崩溃?
当有 50 种图形,需要支持 20 种操作时:
- 类臃肿:每个图形类有 20 个方法,类变得巨大
- 违反开闭原则:新增"导出 YAML"操作,50 个类都要修改
- 职责混乱:图形类既要表示形状,又要处理导出、渲染、计算
- 无法独立演化:图形结构和操作逻辑耦合,难以分别维护
- 团队协作冲突:不同开发者修改不同操作,却都要修改同一个图形类
核心矛盾:数据结构相对稳定,但操作经常增加和变化。
3️⃣ 突破:必须引入什么新维度?
将操作封装为独立的访问者类。
不是"在每个类中添加方法",而是"创建一个访问者类,它知道如何对所有图形执行某个操作"。图形类只提供一个 accept 方法接收访问者,访问者实现对所有图形的具体操作:
图形结构(稳定)
|
v
accept(访问者) --双分派--> 访问者.visit具体图形
|
v
XML导出访问者 / JSON导出访问者 / 面积计算访问者
这就是访问者的本质:通过双分派,将操作从数据类中分离,让两者独立演化。
📊 视觉骨架
接受 accept
访问 visit
访问 visit
访问 visit
实现 implement
实现 implement
实现 implement
元素 Element
访问者 Visitor
具体元素A Concrete A
具体元素B Concrete B
具体元素C Concrete C
XML导出者 XML Exporter
JSON导出者 JSON Exporter
面积计算器 Area Calculator
关键洞察 :访问者模式的核心是"双分派"。element.accept(visitor) 调用后,元素反过来调用 visitor.visit(this),根据元素的实际类型和访问者的实际类型共同决定执行哪个方法。这让新增操作只需要新增访问者类。
⚖️ 权衡模型
公式:
Visitor = 解决了操作的独立扩展 + 牺牲了元素类型的扩展性 + 增加了双分派的复杂性
代价分析:
- ✅ 解决: 操作与数据结构解耦、新增操作不影响元素类、相关操作集中在访问者中、可以累积状态(访问者可以保存遍历状态)
- ❌ 牺牲: 新增元素类型困难(需要修改所有访问者)、破坏了元素的封装(访问者需要访问元素内部)
- ⚠️ 增加: 双分派的理解成本、循环依赖风险(元素依赖访问者接口,访问者依赖具体元素)
使用建议:当对象结构相对稳定,但操作经常增加或变化时使用。典型场景:编译器 AST 操作、报表生成、复杂对象结构的多种导出格式。
🔁 记忆锚点
python
class Shape(ABC):
"""
元素:定义接受访问者的接口
"""
@abstractmethod
def accept(self, visitor: "Visitor") -> None:
"""双分派的入口"""
pass
class Circle(Shape):
"""具体元素:将操作委托给访问者"""
def accept(self, visitor: "Visitor") -> None:
visitor.visit_circle(self) # 关键:告诉访问者"我是圆形"
def __init__(self, radius: float):
self.radius = radius
class Visitor(ABC):
"""
访问者:为每种元素定义操作
"""
@abstractmethod
def visit_circle(self, circle: Circle) -> None:
pass
@abstractmethod
def visit_rectangle(self, rect: "Rectangle") -> None:
pass
class AreaCalculator(Visitor):
"""具体访问者:计算面积"""
def __init__(self):
self.total_area = 0
def visit_circle(self, circle: Circle) -> None:
self.total_area += 3.14159 * circle.radius ** 2
def visit_rectangle(self, rect: "Rectangle") -> None:
self.total_area += rect.width * rect.height
# 使用:遍历结构,让访问者执行操作
shapes = [Circle(5), Rectangle(4, 5)]
calculator = AreaCalculator()
for shape in shapes:
shape.accept(calculator) # 双分派
print(f"Total area: {calculator.total_area}")
一句话本质: 访问者模式 = 通过双分派将操作从数据类中分离,实现操作的独立扩展。