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

news2024/12/26 23:56:07

一、什么是责任链模式

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

        像上图这个审核流程,从开始到结束,需要五步,这五步组成了一个链路,这个就可以看作责任链,当然,我们可以通过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);
    }

    @Override
    public 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);
    }

    @Override
    public 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);
    }

    @Override
    public 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.coloradmin.cn/o/1224381.html

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

相关文章

RobotFramework之如何使用数据驱动(十二)

学习目录 引言 数据驱动是什么&#xff1f; 非驱动方式测试案例 通过添加Template模板的方式&#xff0c;实现数据驱动 将参数放在变量文件中&#xff0c;实现数据驱动 引言 大家平时在写接口或者UI自动化用例的时候&#xff0c;是否遇到这种情况&#xff1a; 写了很多条…

手搓哈希表、列表、队列,只为了用C语言快速求解华容道游戏,我不是大佬,只是一个游戏算法爱好者

背景 多年前曾经写过C语言求解华容道&#xff0c;当时没有用到哈希表&#xff0c;导致整个查重搜索数组过大&#xff0c;每次求解都得花上数分钟的时间&#xff0c;如今时过境迁&#xff0c;对数据结构和算法有了更深的理解&#xff0c;所以得把这一块补上了。(其实就是最近想…

UE5制作场景时的小技巧和注意事项

UE5制作场景时的小技巧和注意事项 一、场景相关 1.1灯光 1.1.1构建完光照,发现场景都是黑的 可能是所有灯光是静态灯光,把skylight改为动态,如果改完之后还是黑色的,那就在构建一次,就应该没问题了 1.1.2场景中有多个动态光会造成阴影闪烁 需要将skylight变为固定 1…

CI/CD - jenkins

目录 一、部署 1、简介 2、部署 二、配置 三、实时触发 四、自动化构建docker镜像 五、通过ssh插件交付任务 六、添加jenkins节点 七、RBAC 八、pipeline 九、jenkins结合ansible参数化构建 1、安装ansible 2、新建gitlab项目 3、jenkins新建项目playbook 一、部…

linux进程之进程的优先级➕环境变量

文章目录 1.优先级的认识1.1优先级的介绍1.2初识优先级1.3ps指令1.4查看/修改进程的优先级1.5对优先级的认识1.6对进程的深一步理解 2.环境变量2.0环境变量相关的命令2.1环境变量的概念2.2常见/查看环境变量2.3环境变量的作用2.4修改环境变量1.将zombie可执行程序放到PATH现有的…

IOS object-c大屏图表 PNChart 折线图 曲线图

折线图是排列在工作表的列或行中的数据可以绘制到折线图中。折线图可以显示随时间&#xff08;根据常用比例设置&#xff09;而变化的连续数据&#xff0c;因此非常适用于显示在相等时间间隔下数据的趋势。在折线图中&#xff0c;类别数据沿水平轴均匀分布&#xff0c;所有值数…

​软考-高级-系统架构设计师教程(清华第2版)【第19章 大数据架构设计理论与实践 (P691~716)-思维导图】​

软考-高级-系统架构设计师教程&#xff08;清华第2版&#xff09;【第19章 大数据架构设计理论与实践 &#xff08;P691~716&#xff09;-思维导图】 课本里章节里所有蓝色字体的思维导图

AI Navigation导航系统_unity基础开发教程

AI Navigation导航系统 安装插件烘焙导航系统障碍物创建人物的AI导航动态障碍物 在unity编辑器中&#xff0c;有一个灰常好用的插件&#xff1a;Navigation。有了它1&#xff0c;你就可以实现人物自动走到你鼠标点击的位置&#xff0c;而且还会自动避开障碍物&#xff0c;下面就…

7、传统CV之高斯滤波

这一节在上一节均值滤波的基础上,再进阶一下,了解一下什么是高斯滤波。 首先,如上一节所说,均值滤波是利用一个窗口在图片上滑动,每次都计算窗口内能看到的像素的平均值,然后将平均值作为滤波的输出,从而可以起到平滑图像、去噪点的作用。 有没有发现,此时并没有特别…

SARAS-Net: Scale and Relation Aware Siamese Network for Change Detection

SARAS-Net&#xff1a;用于变化检测的尺度和关系感知的孪生网络 AAAI Chao-Peng Chen, Jun-Wei Hsieh, Ping-Yang Chen, Yi-Kuan Hsieh, Bor-Shiun Wang 2023 摘要&#xff1a;变化检测(CD)旨在找出不同时间两幅图像之间的差异&#xff0c;并输出变化图来表示该区域是否发生了…

C++17中std::variant的使用

可变参数模板类std::variant表示类型安全联合体(type-safe union)。std::variant的实例在任何给定时间要么保存其替代类型之一的值&#xff0c;要么在错误的情况下无值。 与union一样&#xff0c;如果std::variant保存某个对象类型T的值&#xff0c;则T的对象表示形式将直…

heatmap | cell cycle genes in Seurat

目的&#xff1a;使用bulk 数据&#xff0c;查看HeLa 双胸苷阻断法 细胞同步化 释放 [0, 3, 4.5, 6, 9, 10.5, 12, 15, 18, 19.5, 21, 22.5, 25.5, 30] 小时后 cell cycle 基因的表达情况。 1.结果 S phase G2M phase S G2M phase 不方便看&#xff0c;横过来看&#xff1a;…

Windows上搭建一个网站(基本生产环境)

前言 本博客记录的是Windows上一次网站搭建的过程&#xff0c;主要是在前端采用的是React&#xff0c;后端采用的是Flask&#xff0c;记录一下生产版本搭建流程和坑点&#xff0c;供有缘人一起进步&#xff0c;当然本博客还存在很多不足。 前端项目构建生产版本 以React为例…

【C++】容器string的常用成员函数接口

目录 string - C Reference 1 容量相关 1.1 size/length 1.2 capacity 1.3 resize 1.4 reserve 1.5 empty 2 运算符重载 2.1 operator 2.2 operator[] 2.3 operator&#xff08;非成员函数&#xff09; 2.4 operator 2.5 operator>> && operator<…

​软考-高级-系统架构设计师教程(清华第2版)【第20章 系统架构设计师论文写作要点(P717~728)-思维导图】​

软考-高级-系统架构设计师教程&#xff08;清华第2版&#xff09;【第20章 系统架构设计师论文写作要点&#xff08;P717~728&#xff09;-思维导图】 课本里章节里所有蓝色字体的思维导图

人工智能-循环神经网络通过时间反向传播

到目前为止&#xff0c;我们已经反复提到像梯度爆炸或梯度消失&#xff0c; 以及需要对循环神经网络分离梯度。 例如&#xff0c;我们在序列上调用了detach函数。 为了能够快速构建模型并了解其工作原理&#xff0c; 上面所说的这些概念都没有得到充分的解释。 本节将更深入地探…

SpringSecurity6 | 自动配置(下)

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; Java从入门到精通 ✨特色专栏&#xf…

CCF CSP认证 历年题目自练Day47

题目 试题编号&#xff1a; 201712-3 试题名称&#xff1a; Crontab 时间限制&#xff1a; 10.0s 内存限制&#xff1a; 256.0MB 样例输入 3 201711170032 201711222352 0 7 * * 1,3-5 get_up 30 23 * * Sat,Sun go_to_bed 15 12,18 * * * have_dinner 样例输出 201711170…

51.Sentinel微服务保护

目录 &#xff08;1&#xff09;初识Sentinel。 &#xff08;1.1&#xff09;雪崩问题及解决方案。 &#xff08;1.1.1&#xff09;雪崩问题。 &#xff08;1.1.2&#xff09;解决雪崩问题的四种方式。 &#xff08;1.1.3&#xff09;总结。 &#xff08;1.2&#xff09;…

【LeetCode刷题-滑动窗口】--345.反转字符串中的元音字母

345.反转字符串中的元音字母 class Solution {public String reverseVowels(String s) {int len s.length();if(len < 2){return s;}char[] charArray s.toCharArray();int left 0,right len - 1;while(true){while(left < len && checkVowels(charArray[lef…