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

news/2025/10/6 8:43:42/文章来源:https://www.cnblogs.com/slgkaifa/p/19127264

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

摘要

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

1. 访问者设计模式定义

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

1.1. 关键角色说明

角色

说明

Element

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

ConcreteElement

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

Visitor

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

ConcreteVisitor

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

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

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

通过访问者模式,你可以创建多个 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;
}// 提供访问者访问自己
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}// getter
public String getName() { return name; }
public double getSalary() { return salary; }
}
public class Department implements Element {
private String deptName;public Department(String deptName) {
this.deptName = deptName;
}@Override
public 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 {
@Override
public void visit(Employee employee) {
System.out.println("员工:" + employee.getName() + ",薪资:" + employee.getSalary());
}@Override
public 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;@Override
public void accept(RiskVisitor visitor) {
visitor.visit(this);
}
}
@Data
public class CompanyUser implements User {
private String companyName;
private String licenseNumber;
private double registeredCapital;@Override
public 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 {@Override
public void visit(PersonalUser personalUser) {
System.out.println("[信用评分] 用户 " + personalUser.getName() + " 分数: " + personalUser.getCreditScore());
}@Override
public void visit(CompanyUser companyUser) {
System.out.println("[信用评分] 公司 " + companyUser.getCompanyName() + " 注册资本: " + companyUser.getRegisteredCapital());
}
}

5.3.2. 欺诈检测访问者

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

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

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

5.5. 启动与使用

@SpringBootApplication
public class RiskApp implements CommandLineRunner {@Autowired
private RiskEvaluationService evaluationService;@Override
public 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)

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

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

博文参考

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

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

相关文章

个人网站的制作步骤企业网站设计步骤

Redis 提供了两种主要的方式来执行模糊查询Key的操作&#xff1a; 方法1&#xff1a;KEYS 命令 1KEYS pattern KEYS 命令允许你按照给定的模式来查找数据库中的所有匹配项。例如&#xff1a; 1redis> KEYS user* 这条命令会返回所有以 "user" 开头的key。 然…

济南网站建设公司有哪些甘肃企业网络推广软件

1402. 做菜顺序 原题地址&#xff1a; 力扣每日一题&#xff1a;做菜顺序 一个厨师收集了他 n 道菜的满意程度 satisfaction &#xff0c;这个厨师做出每道菜的时间都是 1 单位时间。 一道菜的 「 like-time 系数 」定义为烹饪这道菜结束的时间&#xff08;包含之前每道菜所花…

网站开发 百度网盘wordpress水印图片插件

1、正常终止 从main函数返回调用exit(标准C库函数)调用_exti或_Exit&#xff08;系统调用&#xff09;最后一个线程从其启动例程返回最后一个线程调用 pthread exit 2、异常终止 调用abort接受到一个信号并终止最后一个线程对取消请求做处理响应 3、进程返回 通常程序运行…

招生网站模板网易企业邮箱可以保存多少邮件

随着网络的快速发展&#xff0c;当今社会存在的网络安全问题也是接踵而来&#xff1a;网络入侵、网络攻击等非法活动威胁信息安全&#xff1b;非法获取公民信息、侵犯知识产权、损害公民合法利益&#xff1b;宣扬恐怖主义、极端主义&#xff0c;严重危害国家安全和社会公共利益…

网站建设方案书组网方案做网站需要什么技能

1&#xff09; 观察者模式定义 略&#xff0c;各种设计模式的书上都有定义。 2&#xff09; 观察者模式一般实现 观察者模式一般实现&#xff0c;都是“被观察者”保存一个“观察者”的列表&#xff0c;循环这个列表来通知“观察者”。代码&#xff0c;其中使用了boost的智能…

关于建设网站的申请怎样攻击网站

基于WIN10的64位系统演示 一、写在前面 上期我们基于TensorFlow环境介绍了多分类建模的误判病例分析。 本期以健康组、肺结核组、COVID-19组、细菌性&#xff08;病毒性&#xff09;肺炎组为数据集&#xff0c;基于Pytorch环境&#xff0c;构建SqueezeNet多分类模型&#xf…

wordpress 后台密码错误新手seo入门教程

一、告警与通知 告警与通知是服务监控平台的主要输出&#xff0c;但二者是又一定差别的。 告警会在某些时间发生时&#xff08;如指标达到阈值&#xff09;时触发。然而&#xff0c;这并不一定意味着有人被告知此事件的发生&#xff09;这是通知的来源。 所谓通知&#xff0…

港专专利申请量被反超,背后是谁在“偷家”?

微信视频号:sph0RgSyDYV47z6快手号:4874645212抖音号:dy0so323fq2w小红书号:95619019828B站1:UID:3546863642871878B站2:UID: 3546955410049087在先前的文章中,我们注意到,港专的年度专利申请递交量自2005年开…

推广哪个平台好英文网站seo方案

文章目录 首先登录使用获取手机号码双token验证关于校验 首先登录使用 获取openid 获取openid 是在微信登录成功之后返回的信息中 有这个openid 那么第一步就是进行登录 登录是get请求,然后使用的参数有 appid 还有秘钥 还有登录code这个是前端获取的,前端调用登录接口 然后…

Wordpress主页不要全部显示天津seo实战培训

简单贪吃蛇模拟&#xff08;C语言版本&#xff09; 一、所需win32 API知识二、游戏逻辑实现 一、所需win32 API知识 1.在这儿&#xff0c;直接弱化概念&#xff0c;把在贪吃蛇中用到的API知识说一下&#xff01;  1.1用cmd命令来设置控制台窗口的长宽   1.2.用title 指令…

版权诉讼下的MiniMax:AI独角兽的上市迷途

微信视频号:sph0RgSyDYV47z6快手号:4874645212抖音号:dy0so323fq2w小红书号:95619019828B站1:UID:3546863642871878B站2:UID: 3546955410049087 添加图片注释,不超过 140 字(可选)AI产业的终极竞争,终将是技…

Unity UI 性能优化终极指南 — Image篇 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

HTB Eureka靶机渗透实战 - Spring Boot堆转储与Bash算术注入漏洞利用

本文详细记录了HTB Eureka靶机的完整渗透过程,从Spring Boot Actuator堆转储端点暴露导致的凭证泄露,到Spring Cloud Gateway配置劫持,最后通过Bash算术注入实现权限提升。侦察 Nmap扫描发现三个开放端口:SSH (22)…

电子书网站怎么做中国建设工程信息网官网查询

2019独角兽企业重金招聘Python工程师标准>>> 没什么想说的&#xff0c;除了感谢和继续努力外&#xff0c;感谢所有的 oscers 们、感谢 OSC 曾经和现在的小伙伴、感谢我们的合作伙伴。 今年还有4个月&#xff0c;主要工作安排包括&#xff1a; TeamOSC 上线 PaaSO…

吉安市建设规划局网站中山网站网站建设

文章目录 【计算机组成原理2016年真题44题-9分】【第一步&#xff1a;信息提取】【第二步&#xff1a;具体解答】 【计算机组成原理2016年真题45题-14分】【第一步&#xff1a;信息提取】【第二步&#xff1a;具体解答】 【计算机组成原理2016年真题44题-9分】 假定CPU主频为5…

实用指南:Matlab实现LSTM-SVM回归预测,作者:机器学习之心

实用指南:Matlab实现LSTM-SVM回归预测,作者:机器学习之心2025-10-06 08:13 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !importa…

上海代理记账公司排名黑帽seo是什么意思

Nimbus server, 首先从启动命令开始, 同样是使用storm命令"storm nimbus”来启动看下源码, 此处和上面client不同, jvmtype"-server", 最终调用"backtype.storm.daemon.nimbus"的mainnimbus是用clojure实现的, 但是clojure是基于JVM的, 所以在最终发布…

手机照片太多了存哪里? - 实践

手机照片太多了存哪里? - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco&quo…

时隔十六年的南京之旅

上一次来,还是一个没有灌木高的小娃娃。我但凡想起南京,是听不懂的金陵话,早上吃的鱼香肉丝包,中午回家要洗的澡,和晚上坐在地板上看大圣哥玩PSP。那是一段非常久远的回忆了,我在尽力的套用相处定理,一旦回到过…

实用指南:Python编程基础(四) | if语句

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …