urlname
type
Post
password
SyncToConfluence
category
业务技能
date
Apr 2, 2025
slug
4447c08a-9e7b-4828-a64a-e3cdfcd37ba8
icon
Button
catalog
summary
tags
学习笔记
专业能力
设计模式
编程基础
cover
Status
BusyTime
Status 1
status
Published
什么是访问者模式(Visitor Pattern)
访问者模式表示一个作用于某对象结构(例如,一个包含多种不同类型对象的集合或树)中各元素的操作。它允许你在不改变各元素的类的前提下,定义作用于这些元素的新操作。
想象一下你有一组不同的乐器(对象结构 Elements):钢琴、吉他、小提琴。现在你想为它们增加一些新的行为,比如“调音”或“清洁”。
- 不好的方式: 修改 Piano, Guitar, Violin 类,分别添加 tune() 和 clean() 方法。这会污染这些类,并且每次想增加新行为(如“评估价值”)时都要再次修改它们。
- 访问者模式的方式:
- 定义一个“访问者”接口 (InstrumentVisitor),包含 visit(Piano p), visit(Guitar g), visit(Violin v) 等方法。
- 创建具体的访问者类,如 TunerVisitor 和 CleanerVisitor,实现 InstrumentVisitor 接口。TunerVisitor 的 visit(Piano p) 方法知道如何调钢琴,visit(Guitar g) 知道如何调吉他。CleanerVisitor 同理。
- 让每个乐器类 (Piano, Guitar, Violin) 实现一个 accept(InstrumentVisitor visitor) 方法。这个方法很简单,通常就是调用 visitor.visit(this)。
- 当你想调音时,创建一个 TunerVisitor,然后让乐器集合中的每个乐器 accept 这个 TunerVisitor。乐器会反过来调用访问者对应自己类型的方法,从而执行调音操作。
访问者模式的核心在于将操作(Visitor)与对象结构(Elements)分离,利用双重分派(Double Dispatch)机制在运行时确定要执行的操作。
核心思想
- 定义访问者接口 (Visitor): 声明一组 visit 方法,为对象结构中每一种具体元素 (ConcreteElement) 类型都提供一个对应的 visit 方法。方法的参数就是对应的具体元素类型。例如 visit(ConcreteElementA elementA), visit(ConcreteElementB elementB)。
- 创建具体访问者 (ConcreteVisitor): 实现 Visitor 接口。每一个 ConcreteVisitor 代表一种具体的操作或算法,它为每个 visit 方法提供了具体的实现。
- 定义元素接口 (Element): 声明一个 accept(Visitor visitor) 方法,接收一个 Visitor 对象作为参数。
- 创建具体元素 (ConcreteElement): 实现 Element 接口。accept(Visitor visitor) 方法的实现通常只有一行:visitor.visit(this)。这里的 this 会在编译时解析为当前 ConcreteElement 的具体类型,从而调用 Visitor 中对应类型的 visit 方法。
- 对象结构 (Object Structure): (可选但常见) 一个包含并管理 Element 对象的类(如集合、组合结构)。它通常提供一个方法来遍历所有元素,并让每个元素调用其 accept(visitor) 方法,从而让访问者能够访问到结构中的所有元素。
- 双重分派 (Double Dispatch): 这是访问者模式的关键机制。
- 第一次分派: 客户端调用元素的 element.accept(visitor) 方法。这个调用在运行时确定是哪个具体元素(ConcreteElementA 还是 ConcreteElementB)的 accept 方法被执行。
- 第二次分派: 在 accept 方法内部,调用 visitor.visit(this)。这个调用不仅取决于第一次分派确定的 this 的具体类型,还取决于 visitor 的具体类型(虽然在此模式中,通常是基于 this 的类型来选择 visit 方法重载)。最终,会执行 ConcreteVisitor 中对应 ConcreteElement 类型的那个 visit 方法。
优点
- 易于增加新操作: 只需要增加一个新的 ConcreteVisitor 类即可为对象结构添加新的功能,无需修改现有的 Element 类。符合开闭原则(对操作扩展开放,对元素修改关闭)。
- 将相关操作集中: 与特定操作相关的代码都集中在一个 ConcreteVisitor 类中,而不是分散在各个 Element 类里,便于维护。
- 可以访问元素类的内部状态: Visitor 可以访问元素的公共接口,如果需要,也可以通过特定机制(如友元、包级私有、或元素提供特定接口给 Visitor)访问非公共状态(但这可能破坏封装性)。
- 适用于复杂结构: 特别适合用于操作复杂的对象结构(如 Composite 模式构成的树)。
可能的缺点
- 难以增加新的元素类型: 如果对象结构(Element 继承体系)经常变化,使用访问者模式会很麻烦。每增加一个新的 ConcreteElement,就必须修改所有现有的 Visitor 接口和所有 ConcreteVisitor 类,添加对应的 visit 方法。这违反了开闭原则(对元素扩展关闭)。
- 破坏封装性: Visitor 需要为每个 ConcreteElement 提供方法,这可能要求 Element 暴露比平时更多的内部状态或方法给 Visitor,可能破坏其封装性。
- 访问者逻辑可能变得复杂: 如果操作逻辑复杂,Visitor 类可能会变得庞大。
使用场景
- 当一个对象结构包含多种类型的对象,你希望对这些对象实施一些不同的并且不相关的操作。
- 需要对一个对象结构中的对象进行很多不同的操作,并且你希望避免让这些操作"污染"这些对象的类。
- 对象结构相对稳定,但经常需要在此结构上定义新的操作。
- 编译器: 对抽象语法树 (AST) 进行操作,如类型检查、代码优化、代码生成等,每种操作都可以是一个 Visitor。AST 节点是 Element。
- 文档处理: 对文档对象模型 (DOM) 中的不同元素(段落、图片、表格)执行操作,如格式化、导出(HTML, PDF)、内容提取等。
- 集合处理: 对集合中的不同类型元素执行特定操作。
- Android 中的场景 (相对较少直接应用):
- 自定义 Lint 规则: Android Lint 工具检查代码时,会遍历代码的 AST(抽象语法树)。你可以编写自定义的 Detector(扮演 Visitor 角色),在访问特定类型的 AST 节点(Element)时执行检查逻辑。
- 处理复杂的、异构的数据结构: 如果你从 API 或数据库获取了一个包含多种不同类型对象的复杂数据结构(例如,一个新闻 Feed 包含文本、图片、视频、广告等多种卡片类型),并且需要对这些不同类型的对象执行多种操作(如渲染、序列化、事件上报),可以考虑使用访问者模式。每个卡片类型是 Element,渲染逻辑、序列化逻辑等分别是不同的 Visitor。 (但通常在 Android 中,更可能使用 RecyclerView 配合不同的 ViewHolder 和 ItemViewType 来处理这种异构列表,或者使用多态和接口)。
- 框架或库的设计: 在设计需要对用户提供的、类型多样的对象执行固定操作集的框架时可能会用到。
UML 图
下面是基于“为不同类型的员工计算薪资和生成报告”示例的访问者模式 UML 图:
- Employee (Element): 定义 accept 接口。
- FullTimeEmployee, PartTimeEmployee (ConcreteElement): 实现 accept 方法,调用 visitor.visit(this)。
- EmployeeVisitor (Visitor): 定义针对每种具体员工的 visit 方法。
- SalaryCalculatorVisitor, PerformanceReviewVisitor (ConcreteVisitor): 实现 EmployeeVisitor,分别封装了计算薪资和进行绩效评估的操作逻辑。
- Company (ObjectStructure): 持有员工列表,并提供一个 accept 方法来让访问者遍历所有员工。
- Client: 创建 Company 和员工,创建不同的 Visitor,并调用 company.accept(visitor) 来执行操作。
代码示例
在这个例子中:
- Employee 是 Element 接口,FullTimeEmployee 和 PartTimeEmployee 是 Concrete Elements。
- EmployeeVisitor 是 Visitor 接口,SalaryCalculatorVisitor 和 PerformanceReviewVisitor 是 Concrete Visitors,分别封装了计算薪资和绩效评估的逻辑。
- Company 是 Object Structure,负责持有员工列表并应用 Visitor。
- 客户端代码创建了公司和员工,然后创建了不同的访问者,通过调用 company.accept(visitor) 来执行不同的操作(计算总薪资、进行绩效评估),而无需修改 Employee 类本身。
总结
- 访问者模式允许你在不修改现有对象结构的情况下,向其添加新的操作。
- 它通过双重分派将操作逻辑从元素类中分离到访问者类中。
- 优点是易于添加新操作并集中管理相关逻辑,缺点是难以添加新元素类型且可能破坏元素封装性。
- 在对象结构相对稳定,但操作需求经常变化的场景下非常有用,但在常规 Android 应用开发中直接使用的频率相对较低。
记住它的核心:结构稳定操作多?派个访客(Visitor)走一波,元素只需开门(accept),访客自有妙计(visit)!
- Author:CoderWdd
- URL:https://www.wuinsights.top//article/4447c08a-9e7b-4828-a64a-e3cdfcd37ba8
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts