当前位置: 首页 > news >正文

设计模式(二十四)行为型:访问者模式详解

设计模式(二十四)行为型:访问者模式详解

访问者模式(Visitor Pattern)是 GoF 23 种设计模式中最具争议性但也最强大的行为型模式之一,其核心价值在于将作用于某种数据结构中的各元素的操作分离出来,封装到一个独立的访问者对象中,使得在不改变元素类的前提下可以定义新的操作。它通过“双重分派”(Double Dispatch)机制,解决了在静态类型语言中对异构对象集合进行多态操作扩展的难题。访问者模式是构建编译器(语法树遍历)、文档处理系统、复杂报表生成、UI 渲染引擎、静态代码分析工具等系统的理想选择,是实现“开闭原则”在操作维度上的终极体现。

一、详细介绍

访问者模式解决的是“一个数据结构(如对象树或列表)包含多种类型的元素,且需要对这些元素执行多种不同的、与元素本身无关的操作,且这些操作可能频繁新增”的问题。在传统设计中,通常将操作直接定义在元素类中。这导致:

  • 违反单一职责原则:元素类承担了数据和多种操作的职责。
  • 难以扩展操作:新增操作需要修改所有元素类,违反开闭原则。
  • 代码分散:同一操作的逻辑分散在多个元素类中。

访问者模式的核心思想是:将“数据结构”与“作用于数据的操作”解耦。数据结构中的元素接受一个访问者对象作为参数,回调访问者对象中对应其类型的方法。新的操作只需添加新的访问者类,无需修改任何元素类

该模式包含以下核心角色:

  • Visitor(访问者接口):声明一组 visit() 方法,每个方法对应一种具体的元素类型(如 visit(ElementA), visit(ElementB))。它定义了所有可执行操作的抽象接口。
  • ConcreteVisitor(具体访问者):实现 Visitor 接口,为每种元素类型提供具体的操作实现。每个具体访问者代表一种独立的操作(如打印、计算、导出)。
  • Element(元素接口):声明一个 accept(Visitor) 方法,允许访问者“访问”自身。
  • ConcreteElementA, ConcreteElementB, …(具体元素):实现 Element 接口,实现 accept() 方法。在 accept() 中,调用访问者的 visit(this) 方法,将自身作为参数传入,触发正确的 visit 方法(关键:this 的静态类型是当前类,实现双重分派)。
  • ObjectStructure(对象结构):可选角色,表示包含元素的集合或复合结构(如树、列表)。它提供一个接口,允许访问者遍历其所有元素,并调用每个元素的 accept() 方法。

访问者模式的关键优势:

  • 符合开闭原则(操作维度):新增操作只需添加新的 ConcreteVisitor,无需修改 ElementConcreteElement
  • 符合单一职责原则:元素类只负责数据和 accept,操作逻辑集中在访问者中。
  • 操作集中化:同一操作的逻辑集中在单个访问者类中,易于理解、维护和复用。
  • 支持新操作:可以轻松添加如打印、统计、转换、验证等新操作。

访问者模式的关键挑战(双重分派):

  1. 第一重分派:在 ObjectStructure 中,调用 element.accept(visitor)。由于 element 是多态的,accept() 的调用会根据 element 的实际类型分派到 ConcreteElementA.accept()ConcreteElementB.accept()
  2. 第二重分派:在 ConcreteElementX.accept() 中,调用 visitor.visit(this)this静态类型ConcreteElementX,因此编译器会选择 visitor 上参数类型为 ConcreteElementXvisit 方法。即使 visitor 是多态的,visit 方法的重载选择在编译时基于 this 的静态类型确定。

缺点与限制

  • 违反里氏替换原则accept() 方法暴露了具体类型。
  • 元素类难以修改:新增元素类型需要修改所有 Visitor 接口及其所有实现类,违反开闭原则(结构维度)。
  • 复杂性高:理解双重分派和模式结构需要较高心智负担。
  • 过度设计:对于简单操作或稳定结构,可能不必要。

访问者模式适用于:

  • 数据结构稳定,但操作频繁变化(如编译器 AST)。
  • 需要对复杂对象结构执行多种不同的操作。
  • 操作需要访问元素的私有成员(访问者可通过友元或公共方法访问)。
  • 需要避免在元素类中堆积大量无关操作。

二、访问者模式的UML表示

以下是访问者模式的标准 UML 类图:

implements
implements
implements
implements
implements
calls visit()
calls visit()
calls visit()
contains
calls accept()
«interface»
Visitor
+visit(elementA: ConcreteElementA)
+visit(elementB: ConcreteElementB)
+visit(elementC: ConcreteElementC)
ConcreteVisitor1
+visit(elementA: ConcreteElementA)
+visit(elementB: ConcreteElementB)
+visit(elementC: ConcreteElementC)
ConcreteVisitor2
+visit(elementA: ConcreteElementA)
+visit(elementB: ConcreteElementB)
+visit(elementC: ConcreteElementC)
«interface»
Element
+accept(visitor: Visitor)
ConcreteElementA
+accept(visitor: Visitor)
+operationA()
ConcreteElementB
+accept(visitor: Visitor)
+operationB()
ConcreteElementC
+accept(visitor: Visitor)
+operationC()
ObjectStructure
-elements: List<Element>
+attach(element: Element)
+detach(element: Element)
+accept(visitor: Visitor)

图解说明

  • Element 接口定义 accept(Visitor)
  • ConcreteElementX 实现 accept(),内部调用 visitor.visit(this)
  • Visitor 接口为每种 ConcreteElement 声明一个 visit 重载方法。
  • ConcreteVisitor 实现所有 visit 方法,提供具体操作。
  • ObjectStructure 管理元素集合,并提供 accept(Visitor) 遍历所有元素。

三、一个简单的Java程序实例及其UML图

以下是一个文档处理系统的示例,文档包含段落(Paragraph)、图片(Image)、表格(Table)元素,需要支持打印和统计字数操作。

Java 程序实例
// 访问者接口
interface DocumentElementVisitor {void visit(Paragraph paragraph);void visit(Image image);void visit(Table table);
}// 元素接口
interface DocumentElement {void accept(DocumentElementVisitor visitor);
}// 具体元素:段落
class Paragraph implements DocumentElement {private String text;public Paragraph(String text) {this.text = text;}public String getText() {return text;}// accept 实现:回调访问者,传入自身(this)@Overridepublic void accept(DocumentElementVisitor visitor) {visitor.visit(this); // 双重分派的关键:this 是 Paragraph 类型}public void spellCheck() {System.out.println("🔍 段落拼写检查: " + text);}
}// 具体元素:图片
class Image implements DocumentElement {private String filename;private int width;private int height;public Image(String filename, int width, int height) {this.filename = filename;this.width = width;this.height = height;}public String getFilename() {return filename;}public int getWidth() {return width;}public int getHeight() {return height;}@Overridepublic void accept(DocumentElementVisitor visitor) {visitor.visit(this); // this 是 Image 类型}public void compress() {System.out.println("🗜️  压缩图片: " + filename);}
}// 具体元素:表格
class Table implements DocumentElement {private String[][] data;private int rows;private int cols;public Table(String[][] data) {this.data = data;this.rows = data.length;this.cols = data.length > 0 ? data[0].length : 0;}public String[][] getData() {return data;}public int getRows() {return rows;}public int getCols() {return cols;}@Overridepublic void accept(DocumentElementVisitor visitor) {visitor.visit(this); // this 是 Table 类型}public void validate() {System.out.println("✅ 表格数据验证: " + rows + "x" + cols + " 表格");}
}// 具体访问者:打印访问者
class PrintVisitor implements DocumentElementVisitor {@Overridepublic void visit(Paragraph paragraph) {System.out.println("🖨️  打印段落: \"" + paragraph.getText() + "\"");}@Overridepublic void visit(Image image) {System.out.println("🖼️  打印图片: " + image.getFilename() + " (" + image.getWidth() + "x" + image.getHeight() + ")");}@Overridepublic void visit(Table table) {System.out.println("📊 打印表格: " + table.getRows() + " 行, " + table.getCols() + " 列");for (int i = 0; i < table.getRows(); i++) {for (int j = 0; j < table.getCols(); j++) {System.out.print("[" + table.getData()[i][j] + "] ");}System.out.println();}}
}// 具体访问者:字数统计访问者
class WordCountVisitor implements DocumentElementVisitor {private int wordCount = 0;@Overridepublic void visit(Paragraph paragraph) {String[] words = paragraph.getText().split("\\s+");int count = words.length;wordCount += count;System.out.println("📝 段落字数: \"" + paragraph.getText() + "\" -> " + count + " 词");}@Overridepublic void visit(Image image) {// 图片无文字,不计数,但可记录System.out.println("🖼️  图片: " + image.getFilename() + " (0 词)");}@Overridepublic void visit(Table table) {int count = 0;for (int i = 0; i < table.getRows(); i++) {for (int j = 0; j < table.getCols(); j++) {if (table.getData()[i][j] != null && !table.getData()[i][j].trim().isEmpty()) {count += table.getData()[i][j].split("\\s+").length;}}}wordCount += count;System.out.println("📊 表格字数: " + count + " 词");}// 获取统计结果public int getWordCount() {return wordCount;}
}// 对象结构:文档
class Document {private java.util.List<DocumentElement> elements = new java.util.ArrayList<>();public void addElement(DocumentElement element) {elements.add(element);}public void removeElement(DocumentElement element) {elements.remove(element);}// 接受访问者,遍历所有元素public void accept(DocumentElementVisitor visitor) {for (DocumentElement element : elements) {element.accept(visitor);}}
}// 客户端使用示例
public class VisitorPatternDemo {public static void main(String[] args) {System.out.println("📄 文档处理系统 - 访问者模式示例\n");// 创建文档和元素Document document = new Document();document.addElement(new Paragraph("这是一个关于设计模式的文档。"));document.addElement(new Image("diagram.png", 800, 600));document.addElement(new Paragraph("访问者模式非常强大。"));document.addElement(new Table(new String[][]{{"模式", "类型", "用途"},{"访问者", "行为型", "分离操作"},{"策略", "行为型", "替换算法"}}));document.addElement(new Paragraph("总结:访问者模式适用于稳定结构。"));// 使用打印访问者System.out.println("--- 执行打印操作 ---");PrintVisitor printVisitor = new PrintVisitor();document.accept(printVisitor); // 遍历元素,触发 accept -> visitSystem.out.println("\n--- 执行字数统计操作 ---");WordCountVisitor wordCountVisitor = new WordCountVisitor();document.accept(wordCountVisitor);System.out.println("📊 文档总字数: " + wordCountVisitor.getWordCount() + " 词");// 演示新增操作无需修改元素类System.out.println("\n--- 新增操作:元素类型检查 ---");// 只需定义新访问者class TypeCheckVisitor implements DocumentElementVisitor {@Overridepublic void visit(Paragraph paragraph) {System.out.println("✅ 元素: 段落, 内容长度: " + paragraph.getText().length());}@Overridepublic void visit(Image image) {System.out.println("✅ 元素: 图片, 文件: " + image.getFilename() + ", 尺寸: " + image.getWidth() + "x" + image.getHeight());}@Overridepublic void visit(Table table) {System.out.println("✅ 元素: 表格, 大小: " + table.getRows() + "x" + table.getCols());}}TypeCheckVisitor typeCheckVisitor = new TypeCheckVisitor();document.accept(typeCheckVisitor);}
}
实例对应的UML图(简化版)
implements
implements
implements
implements
implements
implements
calls visit()
calls visit()
calls visit()
contains
calls accept()
«interface»
DocumentElementVisitor
+visit(paragraph: Paragraph)
+visit(image: Image)
+visit(table: Table)
PrintVisitor
+visit(paragraph: Paragraph)
+visit(image: Image)
+visit(table: Table)
WordCountVisitor
-wordCount: int
+visit(paragraph: Paragraph)
+visit(image: Image)
+visit(table: Table)
+getWordCount()
TypeCheckVisitor
+visit(paragraph: Paragraph)
+visit(image: Image)
+visit(table: Table)
«interface»
DocumentElement
+accept(visitor: DocumentElementVisitor)
Paragraph
-text: String
+accept(visitor: DocumentElementVisitor)
+getText()
Image
-filename: String
-width: int
-height: int
+accept(visitor: DocumentElementVisitor)
+getFilename()
Table
-data: String[][]
+accept(visitor: DocumentElementVisitor)
+getData()
Document
-elements: List<DocumentElement>
+addElement(element: DocumentElement)
+accept(visitor: DocumentElementVisitor)

运行说明

  • DocumentElement 定义 accept()
  • Paragraph, Image, Table 实现 accept(),内部调用 visitor.visit(this)
  • DocumentElementVisitor 为每种元素声明 visit 重载。
  • PrintVisitor, WordCountVisitor 实现 visit 方法,提供具体操作。
  • Documentaccept() 遍历所有元素,调用其 accept()
  • 新增 TypeCheckVisitor 无需修改任何元素类,完美体现开闭原则。

四、总结

特性说明
核心目的分离数据结构与操作,支持在不修改元素的情况下新增操作
实现机制双重分派:元素 accept 访问者,访问者 visit 元素
优点符合开闭原则(操作维度)、操作集中化、支持新操作、符合单一职责
缺点违反里氏替换、元素新增困难(违反开闭原则-结构维度)、复杂性高、依赖具体类型
适用场景稳定数据结构(如AST)、多操作需求、编译器、文档处理、报表生成
不适用场景结构频繁变化、操作简单、避免继承/重载的语言

访问者模式使用建议

  • 仅在数据结构
http://www.lryc.cn/news/602575.html

相关文章:

  • ADB+Python控制(有线/无线) Scrcpy+按键映射(推荐)
  • 【学习笔记】AD7708/18(1)-理解官网的参考代码
  • MacBook IOS操作系统格式化U盘FAT32
  • 【深度解析】R语言与作物模型(以DSSAT模型为例)融合应用
  • 分布式微服务--核心组件与架构关系(一)
  • R语言简介(附电子书资料)
  • Leetcode_349.两个数组的交集
  • JavaScript手录09-内置对象【String对象】
  • 6.2 总线事务和定时 (答案见原书 P295)
  • 基于Flask的智能停车场管理系统开发实践
  • C语言:20250728学习(指针)
  • 使用node-cron实现Node.js定时任务
  • Javaweb Day3
  • 主要分布于内侧内嗅皮层的层Ⅲ的网格-速度联合细胞(Grid × Speed Conjunctive Cells)对NLP中的深层语义分析的积极影响和启示
  • 学习人工智能所需知识体系及路径详解
  • BUUCTF-MISC-[HBNIS2018]caesar1
  • 科技风杂志《科技风》杂志社科技风编辑部2025年第19期目录
  • 《Ai智能眼镜的市场定义及用户分析》- 深圳市天趣星空科技有限公司 CEO 王洁
  • 【7.26-7.28胜算云AI日报:首个开源3D世界生成模型腾讯混元、微软预示 8 月 GPT-5 发布、Nemotron推理、商汤悟能、DM夺金】
  • Python 实现多服务器并发启动 SDK-C Master 与 Viewer 的分布式方案
  • 科技赋能成长 脑力启迪未来
  • windows内核研究(异常-CPU异常记录)
  • 计算机视觉---Halcon概览
  • 暑期自学嵌入式——Day10(C语言阶段)
  • 生成器和迭代器的区别
  • 【65 Pandas+Pyecharts | 山东省2025年高考志愿投档数据分析可视化】
  • MCP架构:模型上下文协议的范式革命与工程实践
  • JSBridge原理与实现全解析
  • 嵌入式单片机中位带操作控制与实现
  • flutter使用firebase集成谷歌,苹果登录