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

article/2025/6/20 12:37:32

摘要

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

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.hkcw.cn/article/QFEyMxhTLi.shtml

相关文章

曾怒怼张国伟的北体1米84校花,跳高女神胡麟鹏完成蜕变

初听她的名字,这是男的吧!拿出来手机一搜,满屏的大长腿都要溢出屏幕了,我确信了,是女的!再一看,超高的颜值,高挑的身材,这是模特吧?不不不她是田径运动员,她就是跳高冠军胡麟鹏!她1995年出生在河北,父亲是全国十项全能选手,十项全能是田径运动中全能运动项目的一…

尹锡悦夫妇现身投票站 完成总统选举投票

韩国第21届总统选举的正式投票于当地时间6月3日早上6时开始。当天上午,前总统尹锡悦及其夫人金建希前往首尔市瑞草区的一个投票站完成了投票。4月4日,韩国宪法法院通过了对总统尹锡悦的弹劾裁决,罢免了他的总统职务。根据法律规定,韩国必须在60日内举行新一届总统选举。选举…

女子路上持械打砸多辆车被逮捕 视频曝光引发热议

6月3日上午,一段关于“女子站在车辆引擎盖上持械打砸挡风玻璃”的视频在网络上引发关注。视频显示,在一个十字路口等待左转的新能源轿车上,一名女子站在引擎盖上,用器械击打了挡风玻璃两次。随后,轿车起步左转,女子仰面被顶在挡风玻璃上前行。她还随机击打了另外两辆车,…

警方通报15岁男孩遭7人殴打 因误会引发冲突

四川古蔺县警方于6月2日通报了一起涉及未成年人的暴力事件。据报道,两名未成年人在骑车时发出笑声,被15岁的陈某甲误以为是在嘲笑自己。随后,陈某甲与其他六人一起在地下停车场对这两名未成年人进行了殴打。目前,涉案的七人均已被警方抓获,其中两人因涉嫌犯罪已被刑事拘留…

山西民间网红艺人大刚离世 因酒精中毒不幸去世

山西民间网红艺人大刚离世 因酒精中毒不幸去世。6月1日,短视频账号“大刚演艺传媒”发布讣告称,景旭刚因病不幸离世,享年48岁,定于6月4日出殡。许多网友在评论区表达了对景旭刚的哀思,称赞他多才多艺。次日,景旭刚的妻子确认了这一消息,并透露他是因饮酒过量导致酒精中毒…

印度婆罗门家庭的高消费晚餐 奢华盛宴令人惊叹

印度婆罗门家庭的高消费晚餐 奢华盛宴令人惊叹!宝子们!今天带你们大开眼界,看看印度富人的家庭晚餐到底有多绝!这哪里是吃饭,简直是一场奢华的视觉与味觉盛宴,贫穷真的限制了我的想象力!本以为印度菜就是咖喱、飞饼?大错特错!在印度富人餐桌上,传统美食直接“华丽变身…

<el-date-picker>配置禁用指定日期之前的时间选择(Vue2、Vue3包括时分秒)

今天突然接受到一个离谱的需求&#xff1a;有一个需要配置定时任务开始执行时间的组件&#xff0c;之前的做法都是用<el-form>的rules定义校验规则&#xff0c;也能实现效果&#xff0c;但是今天产品突发奇想&#xff1a;不能选的时间就置灰&#xff08;就是我们说的禁用…

女孩绿灯过马路被骑电瓶大爷撞倒 女孩爸爸:对方上门道歉,让删除视频自己拒绝了

女孩绿灯过马路时被骑电瓶大爷撞倒,事后大爷走了,女孩爸爸:孩子腿受伤问题不大,后面对方上门道歉,让删除视频自己拒绝了。责任编辑:zx0002

小埃梅里晒与欧冠奖杯合照 永恒的荣耀

在欧冠决赛中,扎伊尔-埃梅里替补出场,帮助巴黎圣日耳曼以5-0战胜国际米兰,赢得了冠军。赛后,扎伊尔-埃梅里在社交媒体上分享了自己与奖杯的合照,并写道:“永恒的荣耀!很自豪将这座期待已久的奖杯带回家,作为一个巴黎人尤其如此。”他还表达了对与球迷共同庆祝胜利的期待…

哈佛蒋雨融:没有任何海外国家绿卡 回应网友争议

6月2日,扬子晚报报道,5月29日在哈佛大学毕业典礼上,蒋雨融作为毕业生代表发表演讲,成为哈佛近四百年校史中第一位站在毕业讲台上的中国女生。她的演讲视频在网络上走红后,有人指出她父亲在绿发会的职位可能为她积累国际活动经历提供了便利,这引发了网友们的大量争议。6月…

ROS机器人和NPU的往事和新知-250602

往事&#xff1a; 回顾一篇五年前的博客&#xff1a; ROS2机器人笔记20-12-04_ros2 移植到vxworks-CSDN博客 里面提及专用的机器人处理器&#xff0c;那时候只有那么1-2款专用机器人处理器。 无关&#xff1a; 01&#xff1a; 每代人的智商和注意力差异是如何出现的-250602-…

双向奔赴好暖!赵露思哽咽安慰哭泣粉丝

6月2日,赵露思的微博线下和粉丝见面。粉丝转为露丝淘了一本《关于爱》的书,希望她能更爱自己。“你真的特别棒!你多吃一点好不好,我好心疼啊。”露丝瞬间泪崩,粉丝说让你这么煽情不是故意的,赵露思哽咽安慰哭泣粉丝:“你就故意的。”责任编辑:zx0002

C罗晒照备战欧国联半决赛 国家队报到展英姿

当地时间6月1日,C罗在社交媒体上分享了他抵达葡萄牙国家队报到的照片。照片中,C罗身穿休闲装,戴着墨镜,背着双肩包,手里拉着行李箱。葡萄牙队将在6月份的国际比赛日期间与德国队进行一场欧国联半决赛。胜者将与西班牙和法国之间的胜者争夺2025年欧国联冠军。此前,葡萄牙队…

莫雷加德称期待与樊振东成为队友 共战新赛季

当地时间6月1日,德国萨尔布吕肯乒乓球俱乐部在欧洲乒乓球冠军联赛男团决赛中第三次夺冠,队中的瑞典选手莫雷加德因表现稳定被评为赛事MVP。欧洲乒联发布了莫雷加德的赛后采访,他对樊振东即将加盟表示非常期待。莫雷加德说:“能和樊振东做队友简直不要太爽,他是个超厉害的选…

【Unity】相机 Cameras

1 前言 主要介绍官方文档中相机模块的内容。 关于“9动态分辨率”&#xff0c;这部分很多API文档只是提了一下&#xff0c;具体细节还需要自己深入API才行。 2 摄像机介绍 Unity 场景在三维空间中表示游戏对象。由于观察者的屏幕是二维屏幕&#xff0c;Unity 需要捕捉视图并将…

Ubuntu20.04 LTS 升级Ubuntu22.04LTS 依赖错误 系统崩溃重装 Ubuntu22.04 LTS

服务器系统为PowerEdge R740 BIOS Version 2.10.2 DELL EMC 1、关机 开机时连续按键盘F2 2、System Setup选择第一个 System BIOS 3、System BIOS Setting 选择 Boot Setting 4、System BIOS Setting-Boot Setting 选择 BIOS Boot Settings 5、重启 开启时连续按键盘F11 …

“黑救护车”为何屡禁不绝 高额费用与监管难题

家住广东省湛江市的张理没想到,外公临终前回家的3公里路竟会花费1800元。2024年8月,因心脏病、肿瘤、器官衰竭等多种老年疾病住院两个月后,张理的外公走到了生命的尽头。医生表示已无救治意义,家属决定带老人回家保守治疗。然而,如何回家成了难题。大多数普通汽车拒载病人…

钓鱼佬被困河中男子用无人机吊上岸 暴雨救援展现科技力量

钓鱼佬被困河中男子用无人机吊上岸 暴雨救援展现科技力量!近日,在浙江丽水,一名垂钓爱好者因突降暴雨导致河水暴涨而被困河道中央。情况危急之际,当地无人机飞手陈先生迅速行动,仅用2分钟就将被困者安全吊运至岸边。这架用于救援的无人机原本是用于农业植保作业,其最大起…

白银价格查询接口如何用Java进行调用?

一、什么是白银价格查询接口&#xff1f; 它聚焦于上海黄金交易所、上海期货交易所等权威市场&#xff0c;精准提供白银价格行情数据&#xff0c;助力用户实时把握市场脉搏&#xff0c;做出明智的投资决策。 二、应用场景 分析软件&#xff1a;金融类平台可以集成本接口&…

警方通报6岁女童被拖进小巷 嫌疑人已被拘留

6月3日,湖南娄底市公安局娄星分局发布了一则警情通报。通报称,2025年5月20日下午,嫌疑人刘某某(男,38岁)酒后行至娄底市娄星区涟滨中街附近,拖拽一名6岁女童进入居民区巷子,后被居民及时制止并报警。民警迅速赶到现场将刘某某控制。经侦查,刘某某涉嫌寻衅滋事已被拘留…