设计模式——访问者设计模式(行为型)

摘要

访问者设计模式是一种行为型设计模式,它将数据结构与作用于结构上的操作解耦,允许在不修改数据结构的前提下增加新的操作行为。该模式包含关键角色如元素接口、具体元素类、访问者接口和具体访问者类。通过访问者模式,可以在不改变对象结构的情况下,定义新的操作行为。文章通过示例场景和类图、时序图等详细介绍了访问者设计模式的结构和实现方式,并探讨了其适用场景和实战示例。

1. 访问者设计模式定义

将数据结构与作用于结构上的操作解耦,使得在不修改数据结构的前提下,可以增加新的操作行为。访问者模式允许你在不改变对象结构(如树、图、元素集合)的前提下,定义新的操作行为,通过将这些操作封装到独立的 "访问者" 对象中。

1.1. 关键角色说明

角色

说明

Element

定义 accept(Visitor)接口,让访问者访问自己。

ConcreteElement

实现 Element接口,实现接受访问者的具体逻辑。

Visitor

抽象访问者,定义访问每个元素的接口 visit(ElementA)等。

ConcreteVisitor

实现 Visitor,封装对元素结构的具体操作,比如导出、统计、渲染等行为。

1.2. 示例场景(通俗类比)

假设你有一个对象结构为:公司组织结构,每个节点可以是 员工部门。你希望在不修改员工、部门类的前提下,分别实现:

  • 统计薪资总额
  • 导出组织结构为 HTML
  • 打印汇报关系图

通过访问者模式,你可以创建多个 ConcreteVisitor 来实现上述功能,而无需改动 Element 本身代码

2. 访问者设计模式结构

  1. 访问者 (Visitor) 接口声明了一系列以对象结构的具体元素为参数的访问者方法。 如果编程语言支持重载, 这些方法的名称可以是相同的, 但是其参数一定是不同的。
  2. 具体访问者 (Concrete Visitor) 会为不同的具体元素类实现相同行为的几个不同版本。
  3. 元素 (Element) 接口声明了一个方法来 “接收” 访问者。 该方法必须有一个参数被声明为访问者接口类型。
  4. 具体元素 (Concrete Element) 必须实现接收方法。 该方法的目的是根据当前元素类将其调用重定向到相应访问者的方法。 请注意, 即使元素基类实现了该方法, 所有子类都必须对其进行重写并调用访问者对象中的合适方法。
  5. 客户端 (Client) 通常会作为集合或其他复杂对象 (例如一个组合(opens new window)树) 的代表。 客户端通常不知晓所有的具体元素类, 因为它们会通过抽象接口与集合中的对象进行交互。

2.1. 访问者设计模式类图

2.2. 访问者设计模式时序图

3. 访问者设计模式实现方式

访问者设计模式的实现方式,核心在于:将作用于对象结构的操作行为封装到独立的访问者类中,并通过 accept(Visitor) 方法把访问者“注入”到元素中,从而实现对结构中不同元素的不同处理。

3.1. 步骤 1:定义元素接口 Element

public interface Element {void accept(Visitor visitor);
}

3.2. 步骤 2:实现具体元素类(ConcreteElement)

public class Employee implements Element {private String name;private double salary;public Employee(String name, double salary) {this.name = name;this.salary = salary;}// 提供访问者访问自己@Overridepublic void accept(Visitor visitor) {visitor.visit(this);}// getterpublic String getName() { return name; }public double getSalary() { return salary; }
}
public class Department implements Element {private String deptName;public Department(String deptName) {this.deptName = deptName;}@Overridepublic void accept(Visitor visitor) {visitor.visit(this);}public String getDeptName() { return deptName; }
}

3.3. 步骤 3:定义访问者接口 Visitor

public interface Visitor {void visit(Employee employee);void visit(Department department);
}

3.4. 步骤 4:实现具体访问者(ConcreteVisitor)

public class ReportVisitor implements Visitor {@Overridepublic void visit(Employee employee) {System.out.println("员工:" + employee.getName() + ",薪资:" + employee.getSalary());}@Overridepublic void visit(Department department) {System.out.println("部门:" + department.getDeptName());}
}

3.5. 步骤 5:使用访问者

public class Client {public static void main(String[] args) {List<Element> elements = Arrays.asList(new Employee("张三", 12000),new Employee("李四", 15000),new Department("风控部"));Visitor visitor = new ReportVisitor();for (Element element : elements) {element.accept(visitor);  // 每个元素调用 visitor.visit(this)}}
}

3.6. 说明总结

点位

描述

解耦操作和结构

不改动 Element 的结构代码,就能添加任意多种访问逻辑。

遵循开闭原则

新增操作时,只需新建访问者类即可。

单一职责更清晰

每个访问者只关注自己的行为。

缺点:扩展结构麻烦

每次新增 Element 子类,所有 Visitor都要加新的 visit() 方法。

4. 访问者设计模式适合场景

以下是访问者(Visitor)设计模式适合不适合使用的场景清单,便于你快速判断在实战开发中是否应当使用此模式。

4.1. ✅ 适合使用访问者设计模式的场景

场景

说明

需要对对象结构中不同元素进行不同操作

如处理复杂对象树时,不同节点类型需要不同处理(如 AST 抽象语法树、HTML DOM、组织结构等)。

需要在不修改类的前提下增加新操作

元素类稳定,但经常添加新功能,适合将操作逻辑外移成访问者类。符合开闭原则。

多个操作跨多个类共享处理逻辑

如统计报表、导出功能、数据校验,每种功能可封装为访问者,避免元素类职责膨胀。

数据结构较复杂,逻辑需要分离

尤其在组合模式(Composite)中遍历树状结构时,访问者是理想搭档。

需要记录访问轨迹/数据收集/行为链式执行

访问者可收集上下文数据,实现功能链、审计等操作。

4.2. ❌ 不适合使用访问者设计模式的场景

场景

原因

对象结构不稳定,经常增删元素类型

每新增一个元素类,所有访问者都必须修改,违反开闭原则,维护成本高。

操作种类少,变化不频繁

如果只有一两种操作,直接放到元素类中即可,访问者反而引入了额外复杂性。

操作需要频繁访问元素内部状态/私有字段

访问者访问元素的内部字段时会暴露实现细节,可能破坏封装性。

数据驱动而非行为驱动系统

如果处理逻辑更多是对数据表进行规则驱动处理,不如使用策略模式、责任链、状态机等。

系统对性能极度敏感,层层函数调用不可接受

访问者调用链过长,对性能要求极高的系统中不推荐使用。

4.3. ✅ 实战使用建议

建议点

内容

👍 推荐与组合模式搭配使用

树形结构遍历 + 多种处理逻辑,非常适合访问者模式。

👍 可与责任链、模板方法组合

在访问者中执行链式操作或分步骤逻辑。

⚠️ 避免与频繁变更的领域模型搭配

如果业务模型变化频繁,访问者维护成本非常高。

5. 访问者设计模式实战示例

以下是一个基于访问者设计模式的 Spring 项目实战示例,应用于金融风控场景,使用注解方式注入对象,涵盖了完整的结构。

金融风控中,需要对不同类型的用户(例如:个人、企业)进行多维度风险评估,比如信用评分、交易行为分析、黑名单检查等。不同用户类型暴露的数据结构不同,但我们希望将“风险评估逻辑”从数据结构中解耦出来。

使用访问者模式可实现:

  • 新增风险评估逻辑(访问者)无需修改用户结构;
  • 用户数据结构与评估操作解耦,符合开闭原则。

5.1. 📦 项目结构

risk-visitor
├── model
│   ├── User.java
│   ├── PersonalUser.java
│   └── CompanyUser.java
├── visitor
│   ├── RiskVisitor.java
│   ├── CreditScoreVisitor.java
│   └── FraudCheckVisitor.java
├── config
│   └── VisitorConfig.java
└── RiskEvaluationService.java

5.2. 用户对象层(Element)

public interface User {void accept(RiskVisitor visitor);
}
@Data
public class PersonalUser implements User {private String name;private String idCard;private int creditScore;@Overridepublic void accept(RiskVisitor visitor) {visitor.visit(this);}
}
@Data
public class CompanyUser implements User {private String companyName;private String licenseNumber;private double registeredCapital;@Overridepublic void accept(RiskVisitor visitor) {visitor.visit(this);}
}

5.3. 风控访问者接口与实现

public interface RiskVisitor {void visit(PersonalUser personalUser);void visit(CompanyUser companyUser);
}

5.3.1. 信用评分访问者

@Component
public class CreditScoreVisitor implements RiskVisitor {@Overridepublic void visit(PersonalUser personalUser) {System.out.println("[信用评分] 用户 " + personalUser.getName() + " 分数: " + personalUser.getCreditScore());}@Overridepublic void visit(CompanyUser companyUser) {System.out.println("[信用评分] 公司 " + companyUser.getCompanyName() + " 注册资本: " + companyUser.getRegisteredCapital());}
}

5.3.2. 欺诈检测访问者

@Component
public class FraudCheckVisitor implements RiskVisitor {@Overridepublic void visit(PersonalUser personalUser) {System.out.println("[欺诈检查] 检查身份证是否黑名单:" + personalUser.getIdCard());}@Overridepublic void visit(CompanyUser companyUser) {System.out.println("[欺诈检查] 检查营业执照是否异常:" + companyUser.getLicenseNumber());}
}

5.4. 风控服务类(注解注入访问者)

@Service
public class RiskEvaluationService {private final List<RiskVisitor> visitors;@Autowiredpublic RiskEvaluationService(List<RiskVisitor> visitors) {this.visitors = visitors;}public void evaluate(User user) {for (RiskVisitor visitor : visitors) {user.accept(visitor);}}
}

5.5. 启动与使用

@SpringBootApplication
public class RiskApp implements CommandLineRunner {@Autowiredprivate RiskEvaluationService evaluationService;@Overridepublic void run(String... args) {PersonalUser personalUser = new PersonalUser();personalUser.setName("张三");personalUser.setIdCard("123456789");personalUser.setCreditScore(750);CompanyUser companyUser = new CompanyUser();companyUser.setCompanyName("风控科技");companyUser.setLicenseNumber("XYZ-2025");companyUser.setRegisteredCapital(5000_000);evaluationService.evaluate(personalUser);evaluationService.evaluate(companyUser);}public static void main(String[] args) {SpringApplication.run(RiskApp.class, args);}
}

5.6. ✅ 总结优点

  • 易于扩展新评估策略,无需改动用户结构;
  • Spring 自动注入访问者集合,灵活组合;
  • 清晰分离了数据结构与操作行为。

6. 访问者设计模式思考

访问者设计模式(Visitor Pattern)在实际开发中常常与其他设计模式组合使用,以增强系统的可扩展性、解耦能力和灵活性。下面列出访问者模式常与哪些设计模式组合使用,以及各组合的典型应用场景和优势

6.1. ✅ 访问者模式常用组合设计模式

组合模式

组合目的/优势

应用场景示例

组合模式(Composite)

用于遍历和访问复杂对象结构,访问者可递归处理整个树形结构

文档结构、组织架构、产品分类树等

迭代器模式(Iterator)

统一遍历容器结构,配合访问者实现对集合中元素的操作(如批量处理)

批量风控评估、设备监控列表操作

责任链模式(Chain of Responsibility)

多个访问者对象串联处理,解耦多个处理逻辑,每个访问者判断是否处理

多规则风控审批流程,每个处理节点负责一类校验

模板方法模式(Template Method)

访问者中封装处理通用流程,将子类特定行为抽象为钩子方法

通用风险评估框架,子类定义评分规则

策略模式(Strategy)

将访问者作为策略进行注入或切换,使不同访问行为可配置化

不同国家/行业的风险评估策略

状态模式(State)

被访问对象的状态决定了访问者逻辑流程,用于基于状态执行不同操作

用户行为轨迹、风控状态迁移等

工厂模式(Factory)

访问者工厂根据上下文动态创建访问者对象,适配不同对象结构或执行策略

动态风控策略调度系统,按对象类型或场景创建访问者

观察者模式(Observer)

访问者中执行完后通知监听者,适用于监控、日志、审计等异步行为

风控决策日志记录、报警事件触发

博文参考

  • 访问者设计模式
  • 设计模式之访问者模式 | DESIGN

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

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

相关文章

Vue基础(12)_Vue.js循环语句用法:列表渲染

js补充 术语解释 循环(loop)&#xff1a;最基础的概念, 所有重复的行为。 递归(recursion)&#xff1a; 在函数内调用自身, 将复杂情况逐步转化成基本情况。 (数学)迭代(iterate) &#xff1a;在多次循环中逐步接近结果。 (编程)迭代(iterate) &#xff1a;按顺序访问线性结构中…

Linux入门(十三)动态监控系统监控网络状态

top与ps 命令很相似&#xff0c;它们都是用来显示正在执行的进程&#xff0c;top与ps大的区别是top在执行一段时间可以更新正在运行的进程。 #-d 更新秒数 如果不写-d 那默认是3秒更新 # -i 隐藏不活跃进程 top -d 5交互操作 P 按cpu使用大小排序&#xff0c;默认此项 M 按内存…

Java 中 MySQL 索引深度解析:面试核心知识点与实战

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Java 中 MySQL 索引深度解析&#xff1a;面试…

Kafka集成Flume/Spark/Flink(大数据)/SpringBoot

Kafka集成Flume Flume生产者 ③、安装Flume&#xff0c;上传apache-flume的压缩包.tar.gz到Linux系统的software&#xff0c;并解压到/opt/module目录下&#xff0c;并修改其名称为flume Flume消费者 Kafka集成Spark 生产者 object SparkKafkaProducer{def main(args:Array[S…

debian12.9或ubuntu,vagrant离线安装插件vagrant-libvirt,20250601

系统盘: https://mirror.lzu.edu.cn/debian-cd/12.9.0/amd64/iso-dvd/debian-12.9.0-amd64-DVD-1.iso 需要的依赖包,无需安装ruby( sudo apt install -y ruby-full ruby-dev rubygems,后来发现不安装会有编译警告,还是安装吧 ) ,无需安装 zlib1g-dev liblzma-dev libxml2-de…

2025年软件测试面试八股文(含答案+文档)

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 Part1 1、你的测试职业发展是什么&#xff1f; 测试经验越多&#xff0c;测试能力越高。所以我的职业发展是需要时间积累的&#xff0c;一步步向着高级测试工程师…

[CSS3]响应式布局

导读 响应式就是一套代码, 兼容大中小不同的屏幕, 即网页内容不变, 网页布局随屏幕切换而改变 媒体查询 响应式布局的核心技术是媒体查询 媒体查询可以检测屏幕尺寸, 设置差异化的css 开发中的常用写法 使用范围属性, 划定屏幕范围 max-width 最大宽度min-width 最小宽度 …

在 Windows安装 make 的几种方式

在 Windows 上使用 make&#xff08;通常用于自动化构建 C/C 项目等&#xff09;有几种方法。以下是最常见的几种安装和使用方法&#xff1a; 文章目录 ✅ 方法一&#xff1a;使用 Chocolatey 安装 GNU Make&#xff08;推荐&#xff09;✅ 方法二&#xff1a;使用 WSL&#xf…

深度学习笔记25-RNN心脏病预测(Pytorch)

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、前期准备 1.数据处理 import torch.nn.functional as F import numpy as np import pandas as pd import torch from torch import nn dfpd.read_csv(r&…

Pytorch知识点2

Pytorch知识点 1、官方教程2、张量&#x1f9f1; 0、数组概念&#x1f9f1; 1. 创建张量&#x1f4d0; 2. 张量形状与维度&#x1f522; 3. 张量数据类型➗ 4. 张量的数学与逻辑操作&#x1f504; 5. 张量的就地操作&#x1f4e6; 6. 复制张量&#x1f680; 7. 将张量移动到加速…

池中锦鲤的自我修养,聊聊蓄水池算法

面试如泡池&#xff0c;蓄水似人生 起初你满怀期待跳进大厂池子&#xff0c;以为自己是天选之子&#xff0c;结果发现池子里早挤满了和你一样的“锦鲤候选人”。HR的渔网一撒&#xff0c;捞谁全看概率——这不就是蓄水池算法的精髓吗&#xff1f; 初入池&#xff08;i≤k&…

Linux应用开发之网络套接字编程

套接字&#xff08;Socket&#xff09;是计算机网络数据通信的基本概念和编程接口&#xff0c;允许不同主机上的进程&#xff08;运行中的程序&#xff09;通过网络进行数据交换。它为应用层软件提供了发送和接收数据的能力&#xff0c;使得开发者可以在不用深入了解底层网络细…

小白的进阶之路系列之六----人工智能从初步到精通pytorch数据集与数据加载器

本文将介绍以下内容: 数据集与数据加载器 数据迁移 如何建立神经网络 数据集与数据加载器 处理数据样本的代码可能会变得混乱且难以维护;理想情况下,我们希望我们的数据集代码与模型训练代码解耦,以获得更好的可读性和模块化。PyTorch提供了两个数据原语:torch.utils…

深入理解设计模式之中介者模式

深入理解设计模式之&#xff1a;中介者模式&#xff08;Mediator Pattern&#xff09; 一、什么是中介者模式&#xff1f; 中介者模式&#xff08;Mediator Pattern&#xff09;是一种行为型设计模式。它通过引入一个中介对象&#xff0c;来封装一组对象之间的交互&#xff0…

基于通义千问的儿童陪伴学习和成长的智能应用架构。

1.整体架构概览 我们的儿童聊天助手将采用典型的语音交互系统架构,结合大模型能力和外部知识库: 2. 技术方案分解 2.1. 前端应用/设备 选择: 移动App(iOS/Android)、Web应用,或者集成到智能音箱/平板等硬件设备中。技术栈: 移动App: React Native / Flutter (跨平台…

Python Day40

Task&#xff1a; 1.彩色和灰度图片测试和训练的规范写法&#xff1a;封装在函数中 2.展平操作&#xff1a;除第一个维度batchsize外全部展平 3.dropout操作&#xff1a;训练阶段随机丢弃神经元&#xff0c;测试阶段eval模式关闭dropout 作业&#xff1a;仔细学习下测试和训练代…

WordPress_suretriggers 权限绕过漏洞复现(CVE-2025-3102)

免责申明: 本文所描述的漏洞及其复现步骤仅供网络安全研究与教育目的使用。任何人不得将本文提供的信息用于非法目的或未经授权的系统测试。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权,请及时与我们联系,我们将尽快处理并删除相关内容。 前…

基于Spring Boot 电商书城平台系统设计与实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文…

LeetCode 39.组合总和:回溯法与剪枝优化的完美结合

一、问题本质与形式化定义 1.1 题目形式化描述 输入&#xff1a;无重复整数数组candidates、目标值target输出&#xff1a;所有和为target的组合集合&#xff0c;满足&#xff1a; 元素可重复使用组合内元素非降序&#xff08;避免重复解&#xff09;解集无重复组合 1.2 问…

windows11安装编译QtMvvm

windows11安装编译QtMvvm 1 从github下载代码2 官方的Download/Installtion3 自行构建编译QtMvvm遇到的问题3.1 `qmake`问题执行命令报错原因分析qmake报错:找不到编译器 cl解决方案3.2 `make qmake_all`问题执行命令报错原因分析make命令未识别解决方案3.3 缺少`perl`问题执行…