【设计模式】访问者模式模式

访问者模式(Visitor Pattern)详解


一、访问者模式简介

访问者模式(Visitor Pattern) 是一种 行为型设计模式(对象行为型模式),它允许你在不修改对象结构的前提下,为对象结构中的元素添加新的操作。

你可以这样理解:

“有一个公司组织结构图(包含多个部门、员工),现在你想分别计算工资总额、打印员工名单、生成报表等不同功能。如果每次都要修改每个类来支持新功能,会非常麻烦。而访问者模式就像请来不同的‘专家’(访问者)——一个财务专家算工资,一个人事专家做花名册——他们各自去‘访问’每个员工并完成自己的任务。”

核心思想是:将数据结构与作用于结构上的操作分离

它为操作存储不同类型元素的对象结构提供了一种解决方案。
用户可以对不同类型的元素施加不同的操作。

访问者模式包含以下5个角色
Visitor(抽象访问者)
ConcreteVisitor(具体访问者)
Element(抽象元素)
ConcreteElement(具体元素)
ObjectStructure(对象结构)

在这里插入图片描述


二、解决的问题类型

访问者模式主要用于解决以下问题:

  • 需要对一个复杂的对象结构(如树、列表)执行多种不同的操作,且这些操作可能会频繁增加
  • 不想修改现有类来添加新功能(避免破坏封装性或违反开闭原则)。
  • 希望将相关操作集中在一个类中(即访问者),而不是分散在各个数据类中

三、使用场景

场景示例
编译器设计抽象语法树(AST)的解析、类型检查、代码生成等
文档处理系统对文档中的段落、图片、表格进行渲染、导出PDF、统计字数等
UI组件树操作遍历组件树进行布局、绘制、事件分发等
报表生成对一组对象进行汇总、分析、生成统计报告

一个对象结构包含多个类型的对象,希望对这些对象实施一些依赖其具体类型的操作。
需要对一个对象结构中的对象进行很多不同的且不相关的操作,并需要避免让这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。
对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。


四、核心概念

  1. Visitor(访问者接口):定义对每种元素的访问方法,如 visit(ElementA)visit(ElementB)
  2. ConcreteVisitor(具体访问者):实现访问者接口,完成具体操作(如打印、计算、导出等)。
  3. Element(元素接口):定义 accept(Visitor) 方法,用于接收访问者。
  4. ConcreteElement(具体元素):实现 accept 方法,调用访问者的对应 visit 方法。
  5. ObjectStructure(对象结构):如集合、树等,能枚举元素并让访问者遍历它们。

五、实际代码案例(Java)

我们以一个“文档编辑器”为例,文档包含文本段落和图片,我们需要实现“渲染”和“导出为HTML”两种操作。

1. 定义访问者接口

// 访问者接口
interface DocumentVisitor {void visit(Paragraph paragraph);void visit(Image image);
}

2. 定义元素接口

// 元素接口
interface DocumentElement {void accept(DocumentVisitor visitor);
}

3. 创建具体元素类

// 段落元素
class Paragraph implements DocumentElement {private String content;public Paragraph(String content) {this.content = content;}public String getContent() {return content;}@Overridepublic void accept(DocumentVisitor visitor) {visitor.visit(this); // 反向调用访问者,传入自己}
}// 图片元素
class Image implements DocumentElement {private String url;public Image(String url) {this.url = url;}public String getUrl() {return url;}@Overridepublic void accept(DocumentVisitor visitor) {visitor.visit(this);}
}

4. 创建具体访问者类

// 渲染访问者
class RenderVisitor implements DocumentVisitor {@Overridepublic void visit(Paragraph paragraph) {System.out.println("🖥️ 渲染段落: " + paragraph.getContent());}@Overridepublic void visit(Image image) {System.out.println("🖼️ 渲染图片: [显示图片 " + image.getUrl() + "]");}
}// 导出为HTML访问者
class HtmlExportVisitor implements DocumentVisitor {private StringBuilder html = new StringBuilder();@Overridepublic void visit(Paragraph paragraph) {html.append("<p>").append(paragraph.getContent()).append("</p>\n");}@Overridepublic void visit(Image image) {html.append("<img src=\"").append(image.getUrl()).append("\" />\n");}public String getHtml() {return html.toString();}
}

5. 创建对象结构(文档)

import java.util.ArrayList;
import java.util.List;// 文档结构(对象结构)
class Document {private List<DocumentElement> elements = new ArrayList<>();public void addElement(DocumentElement element) {elements.add(element);}// 接受访问者遍历所有元素public void accept(DocumentVisitor visitor) {for (DocumentElement element : elements) {element.accept(visitor);}}
}

6. 客户端测试类

public class Client {public static void main(String[] args) {Document doc = new Document();doc.addElement(new Paragraph("欢迎使用我们的文档系统。"));doc.addElement(new Image("https://example.com/logo.png"));doc.addElement(new Paragraph("这是一个示例文档。"));System.out.println("=== 渲染文档 ===");RenderVisitor renderVisitor = new RenderVisitor();doc.accept(renderVisitor);System.out.println("\n=== 导出为HTML ===");HtmlExportVisitor htmlVisitor = new HtmlExportVisitor();doc.accept(htmlVisitor);System.out.println(htmlVisitor.getHtml());}
}

输出结果:

=== 渲染文档 ===
🖥️ 渲染段落: 欢迎使用我们的文档系统。
🖼️ 渲染图片: [显示图片 https://example.com/logo.png]
🖥️ 渲染段落: 这是一个示例文档。=== 导出为HTML ===
<p>欢迎使用我们的文档系统。</p>
<img src="https://example.com/logo.png" />
<p>这是一个示例文档。</p>

典型代码

典型的抽象访问者类代码:

abstract class Visitor
{public abstract void Visit(ConcreteElementA elementA);public abstract void Visit(ConcreteElementB elementB);public void Visit(ConcreteElementC elementC){//元素ConcreteElementC的操作代码}
}

典型的具体访问者类代码:

class ConcreteVisitor : Visitor
{
public override void Visit(ConcreteElementA elementA) {//元素ConcreteElementA的操作代码}public override void Visit(ConcreteElementB elementB) {//元素ConcreteElementB的操作代码}
}

典型的抽象元素类代码:

interface Element
{void Accept(Visitor visitor);
}

典型的具体元素类代码:

class ConcreteElementA : Element 
{public void Accept(Visitor visitor) {visitor.Visit(this);}public void OperationA() {//业务方法}
}

典型的对象结构代码:

using System;
using System.Collections.Generic;
class ObjectStructure
{private List<Element> list = new List<Element>(); //定义一个集合用于存储元素对象
//接受访问者的访问操作
public void Accept(Visitor visitor)
{
foreach (Object obj in list){((Element)obj).Accept(visitor); //遍历访问集合中的每一个元素
}
}public void AddElement(Element element){list.Add(element);}
public void RemoveElement(Element element){list.Remove(element);}
}

访问者模式的结构与实现
双重分派机制
(1) 调用具体元素类的Accept(Visitor visitor)方法,并将Visitor子类对象作为其参数
(2) 在具体元素类Accept(Visitor visitor)方法内部调用传入的Visitor对象的Visit()方法,例如Visit(ConcreteElementA elementA),将当前具体元素类对象(this)作为参数,例如visitor.Visit(this)
(3) 执行Visitor对象的Visit()方法,在其中还可以调用具体元素对象的业务方法

ConcreteElementA. Accept(Visitor visitor)↓
ConcreteVisitorA. Visit(ConcreteElementA elementA)<ConcreteVisitorA. Visit(this)>↓
ConcreteElementA. OperationA()

其他案例

  1. 某公司OA系统中包含一个员工信息管理子系统,该公司员工包括正式员工和临时工,每周人力资源部和财务部等部门需要对员工数据进行汇总,汇总数据包括员工工作时间、员工工资等。该公司基本制度如下:
    (1) 正式员工每周工作时间为40小时,不同级别、不同部门的员工每周基本工资不同;如果超过40小时,超出部分按照100元/小时作为加班费;如果少于40小时,所缺时间按照请假处理,请假所扣工资以80元/小时计算,直到基本工资扣除到零为止。除了记录实际工作时间外,人力资源部需记录加班时长或请假时长,作为员工平时表现的一项依据。
    (2) 临时工每周工作时间不固定,基本工资按小时计算,不同岗位的临时工小时工资不同。人力资源部只需记录实际工作时间。
    人力资源部和财务部工作人员可以根据各自的需要对员工数据进行汇总处理,人力资源部负责汇总每周员工工作时间,而财务部负责计算每周员工工资。
    现使用访问者模式设计该系统,绘制类图。

在这里插入图片描述

  1. 购物车
    顾客在超市中将选择的商品,如苹果、图书等放在购物车中,然后到收银员处付款。在购物过程中,顾客需要对这些商品进行访问,以便确认这些商品的质量,之后收银员计算价格时也需要访问购物车内顾客所选择的商品。此时,购物车作为一个ObjectStructure(对象结构)用于存储各种类型的商品,而顾客和收银员作为访问这些商品的访问者,他们需要对商品进行检查和计价。不同类型的商品其访问形式也可能不同,如苹果需要过秤之后再计价,而图书不需要。使用访问者模式来设计该购物过程。

在这里插入图片描述

  1. 奖励审批系统
    某高校奖励审批系统可以实现教师奖励和学生奖励的审批(AwardCheck),如果教师发表论文数超过10篇或者学生论文超过2篇可以评选科研奖,如果教师教学反馈分大于等于90分或者学生平均成绩大于等于90分可以评选成绩优秀奖,使用访问者模式设计该系统,以判断候选人集合中的教师或学生是否符合某种获奖要求。
    在这里插入图片描述

设计结构
在这里插入图片描述


六、优缺点分析

优点描述
符合开闭原则新增操作(访问者)无需修改现有元素类
职责分离将相关操作集中到访问者类中,提高内聚性
便于扩展新操作添加新功能只需新增一个访问者类
缺点描述
增加新元素类困难每新增一个元素类型,所有访问者都要修改接口并实现新方法(违反开闭原则)
破坏封装性访问者可能需要访问元素的内部状态
代码复杂度高引入较多类和双向调用,理解成本较高
性能开销多态调用和递归可能导致性能下降

七、与其它模式对比

模式与访问者模式的区别
策略模式策略是替换算法,访问者是扩展操作
观察者模式观察者是事件通知,访问者是主动遍历
迭代器模式迭代器用于遍历,访问者用于操作+遍历

八、最终小结

访问者模式是一种强大但使用场景有限的设计模式,它特别适合那些数据结构相对稳定,但需要在其上执行多种不同操作的系统。

作为一名 Java 开发工程师,你可能会在以下场景中遇到它:

  • 编译器、解释器等语言处理工具;
  • 复杂的数据模型需要多种展示或处理方式;
  • 报表、导出、统计等横切功能较多的系统。

但也要注意:如果元素类型经常变化,访问者模式会变得难以维护。因此,它更适合“操作多、结构稳”的场景。


📌 一句话总结:

访问者模式就像“外聘专家团队”,他们带着各自的工具(操作),去访问公司里的各个部门(元素),完成专业任务,而无需改变公司原有结构。


建议使用时机:

  • 对象结构稳定,但操作频繁扩展;
  • 多种操作需要集中管理;
  • 愿意接受一定的代码复杂度换取灵活性。

以上内部部分由AI大模型生成,注意识别!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/92848.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/92848.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

比特币现货和比特币合约的区别与联系

一、基本定义项目现货&#xff08;Spot&#xff09;合约&#xff08;Futures / Perpetual&#xff09;本质直接买卖比特币本身买卖比特币价格的衍生品合约所得资产真实的 BTC合约头寸&#xff08;没有直接持有 BTC&#xff09;结算方式交割比特币现金结算&#xff08;多数平台&…

Qt/C++开发监控GB28181系统/实时监测设备在线离线/视频预览自动重连/重新点播取流/低延迟

一、前言说明 一个好的视频监控系统&#xff0c;设备掉线后能够自动重连&#xff0c;也是一个重要的功能指标&#xff0c;如果监控系统只是个rtsp流地址&#xff0c;那非常好办&#xff0c;只需要重新打开流地址即可&#xff0c;而gb28181中就变得复杂了很多&#xff0c;需要多…

此芯p1开发板使用OpenHarmony时llama.cpp不同优化速度对比(GPU vs CPU)

硬件环境 Cix P1 SoC 瑞莎星睿 O6 开发板 rx580显卡 产品介绍&#xff1a; https://docs.radxa.com/orion/o6/getting-started/introduction OpenHarmony 5.0.0 使用vulkan后端的llama.cpp &#xff08;GPU&#xff09; # ./llama-bench -m /data/qwen1_5-0_5b-chat-q2_k.…

Android 四大布局:使用方式与性能优化原理

一、四大布局基本用法与特点1. LinearLayout&#xff08;线性布局&#xff09;使用方式&#xff1a;<LinearLayoutandroid:orientation"vertical" <!-- 排列方向&#xff1a;vertical/horizontal -->android:layout_width"match_parent"android:…

Redis的BigKey问题

Redis的BigKey问题 什么是大Key问题&#xff1f; 大key问题其实可以说是大value问题&#xff0c;就是某个key对应的value所占据的存储空间太大了&#xff0c;所以导致我们在操作这个key的时候花费的时间过长&#xff08;序列化\反序列化&#xff09;&#xff0c;从而降低了redi…

TDengine IDMP 产品基本概念

基本概念 元素 (Element) IDMP 通过树状层次结构来组织数据&#xff0c;树状结构里的每个节点被称之为元素 (Element)。元素是一个物理的或逻辑的实体。它可以是具体的物理设备&#xff08;比如一台汽车&#xff09;&#xff0c;物理设备的一个子系统&#xff08;比如一台汽车的…

专题二_滑动窗口_将x减到0的最小操作数

一&#xff1a;题目解释&#xff1a;每次只能移除数组的边界&#xff0c;移除的边界的总和为x&#xff0c;要求返回你移除边界的最小操作数&#xff01;也就是说你最少花几次移除边界&#xff0c;就能够让这些移除的边界的和为x&#xff0c;则返回这个次数&#xff01;所以这个…

CentOS 7 下通过 Anaconda3 运行llm大模型、deepseek大模型的完整指南

CentOS 7 下通过 Anaconda3 运行llm大模型、deepseek大模型的完整指南A1 CentOS 7 下通过 Anaconda3 运行大模型的完整指南一、环境准备二、创建专用环境三、模型部署与运行四、优化配置常见问题解决B1 CentOS 7 下通过 Anaconda3 使用 CPU 运行 DeepSeek 大模型的完整方案一、…

Flutter应用在Windows 8上正常运行

要让Flutter应用在Windows 8上正常运行,需满足以下前提条件,涵盖系统环境、依赖配置、编译设置等关键环节: 一、系统环境基础要求 Windows 8版本 必须是 Windows 8.1(核心支持),不支持早期Windows 8(需升级到8.1,微软已停止对原版Windows 8的支持)。 确认系统版本:右…

Redis实现消息队列三种方式

参考 Redis队列详解&#xff08;springboot实战&#xff09;_redis 队列-CSDN博客 前言 MQ消息队列有很多种&#xff0c;比如RabbitMQ,RocketMQ,Kafka等&#xff0c;但是也可以基于redis来实现&#xff0c;可以降低系统的维护成本和实现复杂度&#xff0c;本篇介绍redis中实现…

【C++动态版本号生成方案:实现类似C# 1.0.* 的自动构建号】

C动态版本号生成方案&#xff1a;实现类似C# 1.0.* 的自动构建号 在C#中&#xff0c;1.0.*版本号格式会在编译时自动生成构建号和修订号。本文将介绍如何在C项目中实现类似功能&#xff0c;通过MSBuild自动化生成基于编译时间的版本号。 实现原理 版本号构成&#xff1a;主版本…

【算法题】:斐波那契数列

用 JavaScript 实现一个 fibonacci 函数&#xff0c;满足&#xff1a; 输入 n&#xff08;从0开始计数&#xff09;输出第 n 个斐波那契数&#xff08;斐波那契数列从 1 开始&#xff1a;1,1,2,3,5,8,13,21…&#xff09; 示例&#xff1a; fibonacci(0) > 1fibonacci(4) &g…

【YOLOv13[基础]】热力图可视化实践 | 脚本升级 | 优化可视化效果 | 论文必备 | GradCAMPlusPlus, GradCAM, XGradCAM, EigenCAM等

本文将进行添加YOLOv13版本的升级版热力图可视化功能的实践,支持图像热力图可视化、优化可视化效果、 可以选择使用GradCAMPlusPlus, GradCAM, XGradCAM, EigenCAM, HiResCAM, LayerCAM, RandomCAM, EigenGradCAM。一个参数即可设置是否显示检测框等。 原图 结果图

ElasticSearch相关术语介绍

1.RESTful风格程序REST(英文全称为:"Representational State Transfer")指的是一组架构约束条件和原则。它是一种软件架构风格&#xff08;约束条件和原则的集合&#xff0c;但并不是标准&#xff09;。 REST通过资源的角度观察网络&#xff0c;以URI对网络资源进行…

《从零构建大语言模型》学习笔记4,注意力机制1

《从零构建大语言模型》学习笔记4&#xff0c;自注意力机制1 文章目录《从零构建大语言模型》学习笔记4&#xff0c;自注意力机制1前言一、实现一个简单的无训练权重的自注意力机制二、实现具有可训练权重的自注意力机制1. 分步计算注意力权重2.实现自注意力Python类三、将单头…

昇思+昇腾开发板+DeepSeek模型推理和性能优化

昇思昇腾开发板DeepSeek模型推理和性能优化 模型推理 流程&#xff1a; 权重加载 -> 启动推理 -> 效果比较与调优 -> 性能测试 -> 性能优化 权重加载 如微调章节介绍&#xff0c;最终的模型包含两部分&#xff1a;base model 和 LoRA adapter&#xff0c;其中base …

未给任务“Fody.WeavingTask”的必需参数“IntermediateDir”赋值。 WpfTreeView

c#专栏记录&#xff1a; 报错 未给任务“Fody.WeavingTask”的必需参数“IntermediateDir”赋值。 WpfTreeView 生成 解决办法 清理和重新生成项目 完成上述配置后&#xff0c;尝试执行以下步骤&#xff1a; 清理项目&#xff1a;删除 bin 和 obj 文件夹。 重新生成项目&…

[Linux]学习笔记系列 -- [arm][lib]

文章目录arch/arm/lib/delay.cregister_current_timer_delay 注册当前定时器延迟read_current_timer 读取当前定时器drivers/clocksource/timer-stm32.cstm32_clocksource_init STM32 平台上初始化时钟源https://github.com/wdfk-prog/linux-study arch/arm/lib/delay.c regis…

harbor仓库搭建(配置https)

目录 1. 环境准备 2. 配置https的原因 3. 生成ca证书 4. 搭建harbor仓库 5. 访问harbor 6. 修改加密算法 1. 环境准备 需要提前安装docker和docker-compose&#xff0c;harbor仓库版本越新&#xff0c;对应的docker和docker-compose版本越新。 主机IP192.168.48.19dock…

C++多线程服务器

C多线程服务器 因为自己同时在看多本书&#xff0c;之前看过《TCP/IP 网络编程》一书&#xff0c;其中有一个自己编写一个多线程服务器的例子&#xff0c;于是就把代码直接抄了一变。 在学习网络编程前需要先了解网络的7层模型。 具体代码如下&#xff1a; 服务器端&#xff1a…