设计模式-行为型模式-责任链模式

一、什么是责任链模式

        责任链模式是一种设计模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。(摘自百度百科)

        像上图这个审核流程,从开始到结束,需要五步,这五步组成了一个链路,这个就可以看作责任链,当然,我们可以通过if语句,依次写,但是,这样写出来的代码,不优雅,且后续想再加流程或修改某一地方,当看到一堆的if-else时,已经不想改了……

二、场景模拟

        为了应用责任链模式,我这里模拟一个场景--预约活动场景。首先,预约之前,要检查用户是否有资格预约,或者是这个活动还有没有效,或者……。一堆的检查,看下面这个流程图:

        1、活动有效性检查:我们要看此用户预约的活动,是否还在有效期内等等;

        2、活动规则检查:看用户是否符合此活动的规则,例如仅限新用户啊,或者限男女等等;

        3、剩余容量检查:没剩余容量了,用户就无法预约了。

以上就是我们简单的三个需求,当然后续可以扩增,但这里就不赘述,ctrl c+v而已。

三、代码工程

3.1、工程结构

其中:domian放置一些类信息,例如用户User、活动Maneuver;BookingInfo接收用户发送的预约请求; CheckInfo模拟校验链返回信息;枚举类CheckStatus时一些状态枚举信息。基于以上,我们简单实现以下。

3.2、基础代码

User.class(简单模拟用户)

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {// 用户private String name;// 其他信息private String otherInfo;
}

Maneuver.class(简单模拟活动)

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Maneuver {// 名称private String name;// 开始private LocalDateTime startTime;// 结束private LocalDateTime endTime;// 容量private Long capacity;// 其它规则private String rule;
}

BookingInfo.class(简单模拟接收用户发送的预约请求)

@Data
@AllArgsConstructor
@NoArgsConstructor
public class BookingInfo {// 预约用户名private String userName;// 预约活动名private String maneuverName;// 其它信息private String otherInfo;
}

CheckStatus.class(简单模拟状态枚举信息)

public enum CheckStatus {SUCCESS("10001"),ERROR("20001"),EXPIRED("30001"),NO_MANEUVER("40001"),NO_MANEUVER_SEGMENT("40002"),VOLUME_LESS("50001");private final String code;CheckStatus(String code){this.code = code;}public String getCode(){return this.code;}
}

CheckInfo.class(简单模拟校验返回信息)

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CheckInfo {// 状态private CheckStatus checkStatus;// 描述信息private String info;
}

以上,基础代码部分就结束了,接下来,我们来开始模拟业务。

四、业务实现

4.1、使用if-else方式实现

public static void errorDemo(Maneuver maneuver, User user, BookingInfo bookingInfo){LocalDateTime now = LocalDateTime.now();// 1、检验此活动是否有效;if (!now.isBefore(maneuver.getEndTime())) {// ...还有其它校验log.error("活动过期!");return;}// 2、其它关键规则校验;if (!user.getOtherInfo().equals(maneuver.getRule())) {// ...还有其它校验log.error("不符合规则!");return;}// 3、检验此活动是否有剩余容量;if (maneuver.getCapacity() <= 0) {// ...还有其它校验log.error("剩余容量不足");return;}log.info("预约成功!" + bookingInfo);}

然后我们写个测试类,实现一下:

public static void main(String[] args) {// 模拟活动Maneuver maneuver = new Maneuver("预约活动1",LocalDateTime.of(2023, 11, 19, 7, 0),LocalDateTime.of(2023, 11, 19, 19, 0),1000L,"man");// 模拟用户User user = new User("张三", "man");// 模拟预约信息BookingInfo bookingInfo = new BookingInfo("张三", "预约活动1", "...");// 使用if来判断errorDemo(maneuver, user, bookingInfo);}

运行结果:

然后我们修改一下User信息模拟一下失败的场景:

可以看到,确实是能实现功能。但是,我这里if代码块里的功能只是做了简化的描述,真实的业务场景校验规则可能有很多很多的代码逻辑,就会导致一堆一堆的if-else堆积在这里,看着就很恶心。所以,我们要用责任链设计模式来优化它!

4.2、使用责任链设计模式重构

4.2.1、责任链模式结构

         首先我们创建一个CheckLink抽象类,新建类去继承CheckLink,实现他的doCheck()方法,这样可以灵活新建校验;且配合next和实现下灵活搭配。

4.2.2、创建责任链抽象类

public abstract class CheckLink {private String checkLinkName;  // 此校验链名称private CheckLink next;  // 下一个校验链public CheckLink(String checkLinkName) {this.checkLinkName = checkLinkName;}public CheckLink setNext(CheckLink next){this.next = next;return this;}public CheckLink getNext(){return this.next;}public String getCheckLinkName(){return this.checkLinkName;}public abstract CheckInfo doCheck(User user, Maneuver maneuver, BookingInfo bookingInfo);
}

上面重要的信息只有一个,就是next变量,这个变量指向了下一个责任链。如果下一个责任链为空,说明这就是责任链的最后一个了;如果下一个责任链不为空,那就依次往后执行。

4.2.3、实现类模拟检验规则

首先我们模拟活动校验

@Slf4j
public class ManeuverCheck extends CheckLink {public ManeuverCheck(String checkLinkName) {super(checkLinkName);}@Overridepublic CheckInfo doCheck(User user, Maneuver maneuver, BookingInfo bookingInfo) {log.info(getCheckLinkName() + " start ...");LocalDateTime now = LocalDateTime.now();// 1、检验此活动是否有效;if (!now.isBefore(maneuver.getEndTime())) {return new CheckInfo(CheckStatus.EXPIRED, "活动已过期");}// 如果后面没有了,说明校验通过if(getNext() == null){return new CheckInfo(CheckStatus.SUCCESS, "校验通过");}// 如果后面还有校验链,再继续执行return getNext().doCheck(user, maneuver, bookingInfo);}
}

然后模拟规则校验

@Slf4j
public class RuleCheck extends CheckLink {public RuleCheck(String checkLinkName) {super(checkLinkName);}@Overridepublic CheckInfo doCheck(User user, Maneuver maneuver, BookingInfo bookingInfo) {log.info(getCheckLinkName() + " start ...");// 2、其它关键规则校验;if (!user.getOtherInfo().equals(maneuver.getRule())) {return new CheckInfo(CheckStatus.ERROR, "规则校验失败");}// 如果后面没有了,说明校验通过if(getNext() == null){return new CheckInfo(CheckStatus.SUCCESS, "校验通过");}// 如果后面还有校验链,再继续执行return getNext().doCheck(user, maneuver, bookingInfo);}
}

然后模拟容量校验

@Slf4j
public class CapacityCheck extends CheckLink {public CapacityCheck(String checkLinkName) {super(checkLinkName);}@Overridepublic CheckInfo doCheck(User user, Maneuver maneuver, BookingInfo bookingInfo) {log.info(getCheckLinkName() + " start ...");// 3、检验此活动是否有剩余容量;if (maneuver.getCapacity() <= 0) {return new CheckInfo(CheckStatus.VOLUME_LESS, "剩余容量不足");}// 如果后面没有了,说明校验通过if(getNext() == null){return new CheckInfo(CheckStatus.SUCCESS, "校验通过");}// 如果后面还有校验链,再继续执行return getNext().doCheck(user, maneuver, bookingInfo);}
}

当然,有别的校验需求也可以自定义添加,这里就先写三个。让我们来分析一下代码结构,这三个结构都是类似的,看doCheck()方法,首先执行校验内容,如果校验失败,就返回提示信息;如果校验成功,就再判断一下是否处于责任链末端,如果处于责任链末端,说明此责任链已经执行完毕;如果后续还有其他责任链,就传播至下一责任链,以此类推。

4.2.4、运行测试

public static void useResponChainModel(Maneuver maneuver, User user, BookingInfo bookingInfo){CheckLink checkLink = new ManeuverCheck("1、活动校验").setNext(new RuleCheck("2、规则校验").setNext(new CapacityCheck("3、容量校验")));CheckInfo checkInfo = checkLink.doCheck(user, maneuver, bookingInfo);log.info(String.valueOf(checkInfo));}

我们创建一条责任链,依次是:活动校验->规则校验->容量校验,然后执行doCheck()方法,获取CheckInfo信息。

public static void main(String[] args) {// 模拟活动Maneuver maneuver = new Maneuver("预约活动1",LocalDateTime.of(2023, 11, 19, 7, 0),LocalDateTime.of(2023, 11, 19, 19, 0),1000L,"man");// 模拟用户User user = new User("张三", "man");// 模拟预约信息BookingInfo bookingInfo = new BookingInfo("张三", "预约活动1", "...");// 使用责任链useResponChainModel(maneuver, user, bookingInfo);}

可以看到运行结果:

成功触发了我们设置的三个校验类,成功完成了校验。我们将条件修改为不符合,再试一下效果:

可以看到修改条件,将规则校验失败的时候,成功停止了后续的校验,并返回了信息。 

至此,我们就完成了使用责任链设计模式重构if-else判断。

五、心得

        之前我做过档案管理系统,刚开始的时候没学习过设计模式,硬是一堆if-else堆上去,因为档案审核流程复杂而且多变,经常因为需求变动重构代码。如果是刚写完的代码重构还方便点,因为还记得怎么写的。一旦时间久了,忘记了之前的业务逻辑,再想在“屎山”上修改代码,太难了。后续,我自己摸索出了一条经验,用自己的语言描述就是,使用“黑箱”,即将每个if-else块都封装在一个“黑箱”里面,一个操作进来,就进黑箱,然后这个黑箱执行一些操作,然后告诉我们结果。我自己尝试了重构了所有的if-else,发现还可以,效果不错。后来,我又尝试在数据库中存储各个流程线的流程,当以后再有任务进来,我就去数据库里读流程,然后再通过“黑箱”依次校验。再后来直接上flowable流程引擎了……

        直到我偶然间听到设计模式,我就发现,我那个“黑箱”就是责任链的雏形。果然,编程思想都是互通的,如果我早点学习设计模式,也不会走这么多弯路。但是想想走弯路也是有好处的,会让自己对责任链模式的认识更加深刻。

        有同学还可能会有这样的疑问,我用if-else,就几行代码,轻松实现;用上设计模式,又要写抽象类,又要写实现类,代码量和复杂性增加了好几倍,图啥?当然,如果是小型项目,可以用if-else,这一点毛病也没用。如果是大型项目且变动多,就可以考虑责任链模式,这个因人而异。我们使用了责任链模式,可以看到代码结构变得清晰了,降低了耦合度,也让对象间的关系变得清晰,所以在后续的开发中,我们可以试着用一下责任链模式。

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

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

相关文章

Lavarel定时任务的使用

系统为window 执行命令(执行一次命令只会根据当前时间运行一次定时任务) php artisan schedule:run创建一个任务类(在Jobs文件夹下面) <?phpnamespace App\Jobs;use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contract…

VS2019编译安装GDAL(C++)程序库

一、GDAL简介 GDAL&#xff0c;全称Geospatial Data Abstraction Library&#xff0c;即地理空间数据抽象库&#xff0c;是一个在X/MIT许可协议下读写空间数据的开源库&#xff0c;可以通过命令行工具来进行数据的转换和处理。而在调用中我们常用的OGR&#xff08;OpenGIS Simp…

Talk2BEV: Language-enhanced Bird’s-eye View Maps for Autonomous Driving

论文标题为“Talk2BEV: Language-enhanced Bird’s-eye View Maps for Autonomous Driving”&#xff0c;主要介绍了一种新型的视觉-语言模型&#xff08;LVLM&#xff09;界面&#xff0c;用于自动驾驶情境中的鸟瞰图&#xff08;BEV&#xff09;映射。以下是论文的主要内容概…

MATLAB中std函数用法

目录 语法 说明 示例 矩阵列的标准差 三维数组的标准差 指定标准差权重 矩阵行的标准差 数组页的标准差 排除缺失值的标准差 标准差和均值 标准差 std函数的功能是得到标准差。 语法 S std(A) S std(A,w) S std(A,w,"all") S std(A,w,dim) S std(A…

2311rust,到38版本更新

1.35.0稳定版 此版本亮点是分别为Box<dyn FnOnce>,Box<dyn FnMut>和Box<dyn Fn>实现了FnOnce,FnMut和Fn闭包特征. 此外,现在可按不安全的函数指针转换闭包.现在也可无参调用dbg!. 为Box<dyn Fn*>实现Fn*装饰特征. 以前,如果要调用在盒子闭包中存储的…

nvm切换node后,没有npm

当我们想要在不同的 Node.js 版本之间切换的时候&#xff0c;通常会使用 nvm&#xff08;Node Version Manager&#xff09; 来完成。但是&#xff0c;当我们在使用 nvm 切换 Node.js 版本的时候&#xff0c;可能会遇到没有 npm 的情况。这种情况通常发生在我们在新环境或者重新…

Android---Gradle 构建问题解析

想必做 Android App 开发的对 Gradle 都不太陌生。因为有 Android Studio 的帮助&#xff0c;Android 工程师使用 Gradle 的门槛不算太高&#xff0c;基本的配置都大同小异。只要在 Android Studio 默认生成的 build.gradle 中稍加修改&#xff0c;就都能满足项目要求。但是&am…

『vue-router 要点』

参数或查询的改变并不会触发进入/离开的导航守卫&#xff0c;如何解决&#xff1a; 通过观察 $route 对象来应对这些变化&#xff0c; watch: {$route(to, from) {// 对路由变化作出响应...}}使用 beforeRouteUpdate 的组件内守卫。 beforeRouteUpdate(to, from, next) {// re…

面试题c/c++ --STL 算法与数据结构

1.6 STL 模板 模板底层实现&#xff1a;编译器会对函数模板进行两次编译&#xff0c; 在声明的地方对模板代码本身进行编译&#xff0c; 在调用的地方对参数替换后的代码进行编译。 模板传参分析 模板重载 vector 是动态空间&#xff0c; 随着元素的加入&#xff0c; 它的内…

Apache Airflow (十二) :PythonOperator

&#x1f3e1; 个人主页&#xff1a;IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 &#x1f6a9; 私聊博主&#xff1a;加入大数据技术讨论群聊&#xff0c;获取更多大数据资料。 &#x1f514; 博主个人B栈地址&#xff1a;豹哥教你大数据的个人空间-豹…

内网穿透的应用-如何在Docker中部署MinIO服务并结合内网穿透实现公网访问本地管理界面

文章目录 前言1. Docker 部署MinIO2. 本地访问MinIO3. Linux安装Cpolar4. 配置MinIO公网地址5. 远程访问MinIO管理界面6. 固定MinIO公网地址 前言 MinIO是一个开源的对象存储服务器&#xff0c;可以在各种环境中运行&#xff0c;例如本地、Docker容器、Kubernetes集群等。它兼…

HTTPS流量抓包分析中出现无法加载key

HTTPS流量抓包分析(TLSv1.2)&#xff0c;这篇文章分析的比较透彻&#xff0c;就不班门弄斧了 https://zhuanlan.zhihu.com/p/635420027 写个小问题&#xff1a;RSA密钥对话框加载rsa key文件的时候注意不要在中文目录下&#xff0c;否则会提示&#xff1a;“Enter the passwor…

单张图像3D重建:原理与PyTorch实现

近年来&#xff0c;深度学习&#xff08;DL&#xff09;在解决图像分类、目标检测、语义分割等 2D 图像任务方面表现出了出色的能力。DL 也不例外&#xff0c;在将其应用于 3D 图形问题方面也取得了巨大进展。 在这篇文章中&#xff0c;我们将探讨最近将深度学习扩展到单图像 3…

【MySql】13- 实践篇(十一)

文章目录 1. 自增主键为什么不是连续的&#xff1f;1.1 自增值保存在哪儿&#xff1f;1.2 自增值修改机制1.2.1 自增值的修改时机1.2.2 自增值为什么不能回退? 1.3 自增锁的优化1.3.1 自增锁设计历史 2. Insert语句为何很多锁?2.1 insert … select 语句2.2 insert 循环写入2…

以“防方视角”观Shiro反序列化漏洞

为方便您的阅读&#xff0c;可点击下方蓝色字体&#xff0c;进行跳转↓↓↓ 01 案例概述02 攻击路径03 防方思路 01 案例概述 这篇文章来自微信公众号“潇湘信安”&#xff0c;记录的某师傅如何发现、利用Shiro反序列化漏洞&#xff0c;又是怎样绕过火绒安全防护实现文件落地、…

BLIP-2:冻结现有视觉模型和大语言模型的预训练模型

Li J, Li D, Savarese S, et al. Blip-2: Bootstrapping language-image pre-training with frozen image encoders and large language models[J]. arXiv preprint arXiv:2301.12597, 2023. BLIP-2&#xff0c;是 BLIP 系列的第二篇&#xff0c;同样出自 Salesforce 公司&…

C 语言结构体(struct)

C 语言结构体(struct) 在本教程中&#xff0c;您将学习C语言编程中的结构类型。您将借助示例学习定义和使用结构。 在C语言编程中&#xff0c;有时需要存储实体的多个属性。 实体不必仅具有一种类型的所有信息。 它可以具有不同数据类型的不同属性。 C 数组允许定义可存储相…

Java 的异常体系

Java 中 Throwable 是所有异常和错误的超类&#xff0c;两个直接子类是 Error&#xff08;错误&#xff09;和 Exception&#xff08;异常&#xff09; 在Java中&#xff0c;异常的根类是java.lang.Throwable类&#xff0c;而根类又分为两大类&#xff1a;Error和Exception&…

浅谈开源和闭源的认知

目录 在大型模型的发展中&#xff0c;开源和闭源两种截然不同的开发模式扮演着关键的角色。开源模式通过促进技术共享&#xff0c;吸引了大量优秀人才的加入&#xff0c;从而推动了大模型领域的不断创新。与此相反&#xff0c;闭源模式则着重于保护商业利益和技术优势&#xff…

【uni-app】设置背景颜色相关

1. 全局页面背景色设置&#xff1a; 在App.vue的style样式表中设置 <style> page {background-color: #F0AD4E; } </style> 2. 顶部导航栏背景色设置&#xff1a; 在pages.json页面路由中&#xff0c;globalStyle设置 "globalStyle": {"navi…