访问者模式¶
分类:行为型模式。
类结构¶
classDiagram
direction TB
class ObjectStructure {
+accept(Visitor visitor)
}
class Element {
<<interface>>
+accept(Visitor visitor)
}
class ConcreteElementA {
+accept(Visitor visitor)
}
class ConcreteElementB {
+accept(Visitor visitor)
}
class Visitor {
<<interface>>
+visitElementA(ConcreteElementA element)
+visitElementB(ConcreteElementB element)
}
class ConcreteVisitor1
class ConcreteVisitor2
ObjectStructure o-- "0..*" Element : 遍历元素
Element <|.. ConcreteElementA
Element <|.. ConcreteElementB
Visitor <|.. ConcreteVisitor1
Visitor <|.. ConcreteVisitor2
ConcreteElementA ..> Visitor : 调用 visitElementA
ConcreteElementB ..> Visitor : 调用 visitElementB
角色定义¶
| 角色 | 定义 |
|---|---|
| ObjectStructure(对象结构) | 组织元素集合或对象树,并把访问者传递给其中的元素。 |
| Element(元素接口) | 声明接收访问者的 accept() 方法。 |
| ConcreteElementA / ConcreteElementB(具体元素) | 在 accept() 中把自身传给对应的 visitElementA(this) 或 visitElementB(this),共同形成双分派。 |
| Visitor(访问者接口) | 为每一种具体元素类型声明对应的访问操作。 |
| ConcreteVisitor1 / ConcreteVisitor2(具体访问者) | 分别实现一组针对各元素类型的操作,使新操作可以集中定义在访问者中。 |
Head First 示例:菜单与食材的营养分析¶
原书附录回到餐厅与煎饼屋的菜单:顾客希望查询热量、蛋白质、碳水等信息,定制餐点还需要分析具体食材。书中的示意让遍历者引导访问者访问菜单结构并取得状态,把营养分析从菜单对象中分离。
下面采用显式 accept/visit 的**教学建模**来说明这一需求。原书的简图用 Traverser、Visitor、getState() 描述协作;这里的 MenuElement、NutritionVisitor、NutritionReportVisitor 及双分派方法由本笔记定义,不能视为原书源码。图中只示意热量统计,其他营养指标可按相同方式扩展。
| 示例类型 | 模式角色 | 职责 |
|---|---|---|
| MenuElement | 元素接口 | 为菜单、餐点和食材提供接收访问者的统一入口。 |
| Menu | 具体元素/组合对象 | 组织子菜单或餐点,不直接实现营养统计算法。 |
| MenuItem | 具体元素 | 表示一道餐点,并关联构成该餐点的食材。 |
| Ingredient | 具体元素 | 保存食材自身的营养数据,例如热量。 |
| NutritionVisitor | 访问者接口 | 分别定义访问菜单、餐点和食材的操作。 |
| NutritionReportVisitor | 具体访问者 | 实现营养报告逻辑,按食材累加热量,避免再次累加餐点总量。 |
| Traverser | 遍历者/客户 | 遍历菜单结构,对每个元素调用 accept(),把访问者传入。 |
示例 UML¶
classDiagram
direction TB
class MenuElement {
<<interface>>
+accept(NutritionVisitor visitor)
}
class Menu {
+accept(NutritionVisitor visitor)
}
class MenuItem {
+accept(NutritionVisitor visitor)
}
class Ingredient {
+getCalories() double
+accept(NutritionVisitor visitor)
}
class NutritionVisitor {
<<interface>>
+visitMenu(Menu menu)
+visitItem(MenuItem item)
+visitIngredient(Ingredient ingredient)
}
class NutritionReportVisitor {
-double totalCalories
+visitMenu(Menu menu)
+visitItem(MenuItem item)
+visitIngredient(Ingredient ingredient)
}
class Traverser {
+traverse(MenuElement root, NutritionVisitor visitor)
}
MenuElement <|.. Menu
MenuElement <|.. MenuItem
MenuElement <|.. Ingredient
Menu o-- "0..*" MenuElement : 菜单与餐点
MenuItem o-- "0..*" Ingredient : 食材
NutritionVisitor <|.. NutritionReportVisitor
Traverser ..> MenuElement : 遍历并调用 accept
Traverser ..> NutritionVisitor : 传入访问者
MenuElement ..> NutritionVisitor : 派发到具体访问操作
协作过程¶
- 构建包含菜单、餐点和食材的对象结构,再创建营养报告访问者。
- 遍历者访问各元素,调用元素的
accept(visitor);本模型把递归遍历集中在遍历者中。 - 各具体元素把自身传给对应的
visitMenu(this)、visitItem(this)或visitIngredient(this)。 - 访问者在食材访问操作中累加热量,在其他操作中组织报告,从而避免把同一份营养数据重复计算。
场景来源(Google Books 原书公开检索片段;示例类图为本笔记整理):