跳转至

访问者模式

分类:行为型模式。

类结构

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 : 派发到具体访问操作

协作过程

  1. 构建包含菜单、餐点和食材的对象结构,再创建营养报告访问者。
  2. 遍历者访问各元素,调用元素的 accept(visitor);本模型把递归遍历集中在遍历者中。
  3. 各具体元素把自身传给对应的 visitMenu(this)、visitItem(this) 或 visitIngredient(this)。
  4. 访问者在食材访问操作中累加热量,在其他操作中组织报告,从而避免把同一份营养数据重复计算。

场景来源(Google Books 原书公开检索片段;示例类图为本笔记整理):

评论