如何在业务代码中优雅地使用责任链模式

news2024/11/24 19:40:04

8108c64b413db734e51d8e1135db9feb.gif

通过使用责任链模式,我们可以更加灵活和优雅地处理请求,降低代码之间的耦合度,提高代码的可维护性和可扩展性。在一些具有复杂业务逻辑或需要动态处理请求的场景下,使用责任链模式将是一个很好的选择。本文将通过一个具体的示例来详细介绍如何在业务代码中优雅地使用责任链模式。

0b5d2a75e9a77a9c12dff4eafecd1e85.png

引言


在软件开发中,我们经常会遇到一种情况,即一个请求需要经过多个处理节点才能得到最终结果。这种情况下,我们可以使用责任链模式来优雅地处理请求。责任链模式是一种行为设计模式,它可以创建一条由多个处理节点组成的链,每个节点都有机会处理请求,也可以决定将请求传递给下一个节点。这种模式可以将请求的发送者和接收者解耦,从而提高代码的灵活性和可维护性。

责任链模式的使用场景通常包括以下几种情况:

  1. 有多个对象可以处理请求,但具体由哪个对象处理请求在运行时才能确定。

  2. 想要在不明确指定接收者的情况下,向多个对象中的一个提交请求。

  3. 希望动态指定处理请求的对象集合。

4df5a16f823a8df64c4ff72a64fdaf2e.png

责任链模式的定义

  定义

使多个对象都有机会处理请求,从而避免请求的发送者与请求处理者耦合在一起。将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

  类型

对象行为型模式

  实质

责任链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,从而实现请求发送者与请求处理者的解耦。

a3804dd6d6d487b63b94b36dbcf53d8f.png

优雅之处

使用责任链模式的好处和目标主要有以下几点:

  1. 解耦责任链的节点:责任链模式可以将请求的发送者和接收者解耦,使得它们不需要直接相互引用,从而降低了对象之间的耦合度。

  2. 提高代码的灵活性和可维护性:责任链模式可以在运行时动态地改变处理节点的顺序和数量,从而灵活地处理不同的请求,同时也方便了代码的维护和扩展。

  3. 增加新的处理节点方便:由于责任链模式将处理节点解耦,因此可以方便地增加新的处理节点到责任链中,而不需要修改已有的代码。

  4. 动态处理请求:责任链模式允许在运行时根据具体的情况动态地组合和排序处理者,从而实现灵活的请求处理逻辑。

通过使用责任链模式,我们可以更加灵活和优雅地处理请求,降低代码之间的耦合度,提高代码的可维护性和可扩展性。在一些具有复杂业务逻辑或需要动态处理请求的场景下,使用责任链模式将是一个很好的选择。接下来,我们将通过一个具体的示例来详细介绍如何在业务代码中优雅地使用责任链模式。

6cb559a82b59323878ab9d7d4772e45a.png

责任链模式适合应用场景

当程序需要使用不同方式处理不同种类请求 而且请求类型和顺序预先未知时 可以使用责任链模式

该模式能将多个处理者连接成一条链。接收到请求后, 它会 “询问” 每个处理者是否能够对其进行处理。这样所有处理者都有机会来处理请求。

当必须按顺序执行多个处理者时 可以使用该模式

无论你以何种顺序将处理者连接成一条链, 所有请求都会严格按照顺序通过链上的处理者。

如果所需处理者及其顺序必须在运行时进行改变 可以使用责任链模式

如果在处理者类中有对引用成员变量的设定方法, 你将能动态地插入和移除处理者, 或者改变其顺序。

  实现方式
  1. 声明处理者接口并描述请求处理方法的签名。

    确定客户端如何将请求数据传递给方法。最灵活的方式是将请求转换为对象, 然后将其以参数的形式传递给处理函数。

  2. 为了在具体处理者中消除重复的样本代码, 你可以根据处理者接口创建抽象处理者基类。

    该类需要有一个成员变量来存储指向链上下个处理者的引用。你可以将其设置为不可变类。但如果你打算在运行时对链进行改变, 则需要定义一个设定方法来修改引用成员变量的值。

    为了使用方便, 你还可以实现处理方法的默认行为。如果还有剩余对象, 该方法会将请求传递给下个对象。具体处理者还能够通过调用父对象的方法来使用这一行为。

  3. 依次创建具体处理者子类并实现其处理方法。每个处理者在接收到请求后都必须做出两个决定:

  • 是否自行处理这个请求。

  • 是否将该请求沿着链进行传递。

客户端可以自行组装链, 或者从其他对象处获得预先组装好的链。在后一种情况下, 你必须实现工厂类以根据配置或环境设置来创建链。

客户端可以触发链中的任意处理者, 而不仅仅是第一个。请求将通过链进行传递, 直至某个处理者拒绝继续传递, 或者请求到达链尾。

由于链的动态性, 客户端需要准备好处理以下情况:

    • 链中可能只有单个链接。

    • 部分请求可能无法到达链尾。

    • 其他请求可能直到链尾都未被处理。

1f8be445c86ea9253078ed145ecf9e71.png

应用示例

我们拿请假的场景来说明,我们请假时间分为3天,5天,7天等,对应请假天数审批人不同,可能请假3天由你的项目小组长审批即可,但是请假5天可能需要由部门总来审批。在这个场景中就十分适合使用责任链模式进行构建。当然也可以用大量的if-else来处理,但是相对于if-else来讲责任链模式由些好处:

  1. 责任链模式与 if...else 相比,他的耦合性要低一些,因为它将条件判定分散到各个处理类中,并且这些处理类的优先处理顺序可以随意的设定,并且如果想要添加新的 handler 类也是十分简单的,这符合开放闭合原则。

  2. 责任链模式带来了灵活性,但是在设置处理类前后关系时,一定要避免在链中出现循环引用的问题。

40dd4ccd0d6ae75bdc9122ba36d6b198.png

6bfc5ae67c539b525e7035706e91a7cb.png

实现步骤

  循环责任链模式步骤

使用 for 循环来处理责任链可以更加直观地解释责任链模式的执行过程。

假设我们有一个责任链,包含了多个处理器 Processor1、Processor2、Processor3。每个处理器都可以处理请求,如果一个处理器能够处理请求,就处理并结束责任链的执行;如果处理器不能处理请求,就将请求传递给下一个处理器。

我们可以使用一个 for 循环来遍历责任链中的处理器,并依次进行处理。当遇到第一个能够处理请求的处理器时,就处理请求并结束循环。

Request request = new Request(); // 创建请求对象


Processor[] processors = {new Processor1(), new Processor2(), new Processor3()};


for (Processor processor : processors) {
    if (processor.canHandle(request)) {
        processor.process(request);
        break; // 处理请求并结束循环
    }
}

在上述代码中,我们依次遍历责任链中的处理器,通过调用 canHandle 方法判断是否能够处理请求。如果能够处理,则调用 process 方法进行处理,并使用 break 语句结束循环。如果所有处理器都不能处理请求,循环结束后可以进行相应的处理逻辑。

使用 for 循环来处理责任链能够更加清晰地表达责任链模式的执行过程,我们按照顺序遍历处理器,并根据需要进行处理。如果有多个处理器都能处理请求,我们可以通过调整处理器的顺序来确定处理的优先级。同时,我们也可以在循环结束后根据实际需求进行相应的处理,比如报错或给出默认处理等。

996c05be0210ed7deae3f540b2bda455.png

责任链模式的设计思想就是将一个请求沿着一条链传递,每个节点都有机会处理请求或者将其传递给下一个节点。这种设计类似于流水线上的工作流程,每个节点都负责处理某个环节的工作,并将结果传递给下一个节点。使用流程图可以很好地表示责任链模式的执行流程。每个节点可以表示为一个流程图中的步骤,箭头表示请求的传递方向。整个流程图可以清晰地展示责任链模式中各个节点的工作顺序和流程。

通过流程图的抽象表示,我们可以更好地理解责任链模式的执行流程,便于沟通和交流,也有助于设计和实现责任链模式。同时,流程图也可以帮助开发人员更好地理解和维护责任链模式的代码逻辑。

  请假场景步骤

请假场景中的责任链模式可以通过实现一个LeaveRequestProcessor接口来实现,每个具体处理者都实现该接口,根据请假天数来确定是否能够处理请求,以及是否将请求传递给下一个处理者。

使用 for 循环来实现责任链模式可以更加直观地展示责任链的执行过程:

首先,定义LeaveRequestProcessor接口:

public interface LeaveRequestProcessor {
    void processLeaveRequest(LeaveRequest request);
}

TeamLeaderProcessor(项目小组长处理器):

public class TeamLeaderProcessor implements LeaveRequestProcessor {
    public void processLeaveRequest(LeaveRequest request) {
        if (request.getDays() <= 3) {
            // 处理请假请求
            System.out.println("项目小组长批准了请假申请,天数为:" + request.getDays() + "天");
        } else {
            System.out.println("项目小组长无法处理该请假申请");
        }
    }
}

DepartmentManagerProcessor(部门经理处理器):

public class DepartmentManagerProcessor implements LeaveRequestProcessor {
    public void processLeaveRequest(LeaveRequest request) {
        if (request.getDays() > 3 && request.getDays() <= 5) {
            // 处理请假请求
            System.out.println("部门经理批准了请假申请,天数为:" + request.getDays() + "天");
        } else {
            System.out.println("部门经理无法处理该请假申请");
        }
    }
}

CEOProcessor(总经理处理器):

public class CEOProcessor implements LeaveRequestProcessor {
    public void processLeaveRequest(LeaveRequest request) {
        if (request.getDays() > 5 && request.getDays() <= 7) {
            // 处理请假请求
            System.out.println("总经理批准了请假申请,天数为:" + request.getDays() + "天");
        } else {
            System.out.println("总经理无法处理该请假申请");
        }
    }
}

然后,创建一个处理器列表,并使用 for 循环遍历处理器列表来处理请假请求:

List<LeaveRequestProcessor> processors = new ArrayList<>();
processors.add(new TeamLeaderProcessor());
processors.add(new DepartmentManagerProcessor());
processors.add(new CEOProcessor());


// 创建请假请求
LeaveRequest request = new LeaveRequest("John", 5);


for (LeaveRequestProcessor processor : processors) {
    processor.processLeaveRequest(request);
}

在上述代码中,我们创建了一个处理器列表,并依次添加了项目小组长(TeamLeaderProcessor)、部门经理(DepartmentManagerProcessor)和总经理(CEOProcessor)这三个具体处理者。然后,我们使用 for 循环遍历处理器列表,并依次调用每个处理器的 processLeaveRequest 方法来处理请假请求。

每个处理器在处理请求时,根据请假天数来判断是否能够处理请求。如果能够处理请求,则进行处理并结束循环;如果不能处理请求,则继续下一个处理器。这样,请求会依次在处理器列表中传递,直到找到能够处理请求的处理器为止。

通过使用 for 循环来实现责任链模式,我们更加直观地表达了责任链模式的执行过程。同时,通过调整处理器在处理器列表中的顺序,可以灵活地改变处理请求的优先级。在业务逻辑发生变化时,我们只需要新增或修改相应的处理器即可,而不需要修改已有的处理器或请求发送者的代码,提高了代码的可维护性和可扩展性。

这样,通过责任链模式,我们可以灵活地处理请假请求,并且根据请假天数确定由哪个处理者来审批。在业务逻辑发生变化时,我们只需要新增或修改相应的处理者即可,而不需要改动已有的处理者或请求发送者的代码,提高了代码的可维护性和可扩展性。

8f040bf28fb1415bf70d88db7f5e29f8.png

参考资料

  1. https://juejin.cn/post/7273028474981335081责任链的使用

  2. https://refactoringguru.cn/design-patterns/chain-of-responsibility责任链模式

549bb5c1b1acf5b7455ed2ffc9e089fb.png

团队介绍

淘天集团-内容消费&社区互动团队,是阿里的一支明星团队,目前负责阿里电商平台的首页和信息流推荐,其中手机淘宝首页、逛逛、信息流、购后链路等场景每天服务数亿用户,大促核心系统峰值QPS千万级,工作涉及全链路端到端性能优化,流量效率提升、用户体验、提高商家及达人参与淘宝的积极性,优化商业生态运行机制。在过去的几年时间,一直与业界领先的算法团队紧密协作,不断拓展业务边界并将核心业务指标一次次踩在脚下。我们专注手机淘宝首页、淘宝逛逛、推荐信息流核心链路业务支持和业务平台抽象。这里有巨大的流量,可以满足你对高并发大规模分布式系统练手的畅想;这里有最前沿的算法应用场景,可以玩转各种智能创新;这里有最严苛的系统指标要求,可以让你感受到优化复杂系统的快感~

目前正在招聘感兴趣的同学(校招社招均有),欢迎交流沟通或投递简历 linzesi.lzs@taobao.com。

¤ 拓展阅读 ¤

3DXR技术 | 终端技术 | 音视频技术

服务端技术 | 技术质量 | 数据算法

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

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

相关文章

【错误解决方案】ModuleNotFoundError: No module named ‘tensorboardX‘

1. 错误提示 在python程序中&#xff0c;尝试导入一个名为tensorboardX的模块&#xff0c;但Python提示找不到这个模块。 错误提示&#xff1a;ModuleNotFoundError: No module named ‘tensorboardX‘ 2. 解决方案 在python出现中&#xff0c;遇到这个问题是Python无法找到…

无需编程技术,快速搭建个人网站

如果你想拥有一个属于自己的个人网站&#xff0c;但又没有任何编程经验&#xff0c;别担心&#xff0c;我们今天将为你介绍一个简单的方法&#xff0c;让你轻松搭建网站&#xff0c;无需任何编程知识。让我们一起来看看吧&#xff01; 在乔拓云建站工具中&#xff0c;自带了许多…

TypeScript之装饰器

一、是什么 装饰器是一种特殊类型的声明&#xff0c;它能够被附加到类声明&#xff0c;方法&#xff0c; 访问符&#xff0c;属性或参数上 是一种在不改变原类和使用继承的情况下&#xff0c;动态地扩展对象功能 同样的&#xff0c;本质也不是什么高大上的结构&#xff0c;就…

图纸管理制度《八》设计图纸管理制度

第一章 总则 第1条 目的。为做好设计图纸的管理工作&#xff0c;使其收发及时、手续齐全、废图绝迹、不遗失、无差错&#xff0c;特制定本办法。 第2条 适用范围。本办法适用于企业所有工程项目的图纸管理工作。 第3条 相关部门及人员职责 (1) 工程技术部负责图纸管理的监督…

双十一百亿美元补贴,AWS阿里云腾讯云华为云国际版钜惠

双十一来袭&#xff01;阿里云/腾讯云/华为云国际站该怎么玩&#xff1f;九河云&#xff08;双十一特大促销&#xff0c;低至5.18折 (9he.com)&#xff09;这次双十一活动汇聚了一系列前所未有的优惠&#xff0c;不仅能享受服务器和CDN的超值折扣&#xff0c;还有机会赢取华为M…

智慧工地建造平台源码、智慧化工地云平台源码

概述&#xff1a;智慧工地管理平台充分运用数字化技术&#xff0c;聚焦施工现场岗位一线&#xff0c;依托物联网、互联网、AI等技术&#xff0c;围绕施工现场管理的人、机、料、法、环五大维度&#xff0c;以及施工过程管理的进度、质量、安全三大体系为基础应用&#xff0c;实…

Spring Cloud Alibaba中Nacos的安装(Windows平台)以及服务的发现

Spring Cloud Alibaba中Nacos的安装&#xff08;Windows平台&#xff09;以及服务的发现 下载安装Nacos解压启动验证是否启动搭建一个简单的Spring Cloud Alibaba项目Spring Cloud Alibaba 以及 Nacos的引入如何选择对应的版本 服务的注册Nacos相关组件的说明 下载安装Nacos G…

Python如何解析json对象?

目录 一、JSON简介 二、Python的json模块 1. 加载JSON数据 2. 生成JSON数据 三、处理复杂的JSON数据 四、自定义JSON解析器 五、注意事项和最佳实践 六、总结 JSON&#xff08;JavaScript Object Notation&#xff09;作为一种轻量级的数据交换格式&#xff0c;在网络通…

https原理

首先说一下几个概念&#xff1a;对称加密、非对称加密 对称加密&#xff1a; 客户端和服务端使用同一个秘钥&#xff0c;分两种情况&#xff1a; 1、所有的客户端和服务端使用同一个秘钥&#xff0c;这个秘钥被泄漏后数据不再安全 2、每个客户端生成一个秘钥&…

嵌入式每日500(4)231104 (Flash类型定义、Flash常量定义、Flash函数)

文章目录 1.Flash类型定义&#xff08;两个结构体&#xff09;2.Flash常量定义&#xff08;3种&#xff09;3.Flash函数&#xff08;31个&#xff0c;FLASH分为两个区&#xff0c;一个是普通的存储空间&#xff0c;一个是选项字节OB&#xff0c;函数名里带OB的就是对选项字节空…

一文速通Sentinel熔断及降级规则

目录 基本介绍 熔断模式 状态机的三个状态 熔断降级规则 断路器熔断策略 慢调用 异常比例 异常数 基本介绍 熔断模式 主要是参考电路熔断&#xff0c;如果一条线路电压过高&#xff0c;保险丝会熔断&#xff0c;防止火灾。放到我们的系统中&#xff0c;如果某个目标…

Azure 机器学习 - 无代码自动机器学习的预测需求

了解如何在 Azure 机器学习工作室中使用自动化机器学习在不编写任何代码行的情况下创建时序预测模型。 此模型将预测自行车共享服务的租赁需求。 关注TechLead&#xff0c;分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验&#xff0c;同济本复旦硕…

【C语言】指针初阶

✨个人主页&#xff1a; Anmia.&#x1f389;所属专栏&#xff1a; C Language &#x1f383;操作环境&#xff1a; Visual Studio 2019 版本 ​ 1.指针是什么&#xff1f; 指针理解的2个要点&#xff1a; 指针是内存中一个最小单元的编号&#xff0c;也就是地址平时口语中说的…

Vue elemen ui 移除上次校验与部分清除上次校验

场景&#xff1a; 可以切换类型&#xff0c;下面的输入框参数也会随着改变。 如果不清除上次的校验就会出现&#xff0c;之前的大陆企业的校验还会出现在香港企业的校验中 方法&#xff1a; watch:{ruleForm.paymentSubjectType:{ 通过监听表单的类型来调用 clearValidate方…

计算机网络之网络层(全)

网络层的功能 互联网在网络层的设计思路是&#xff0c;向上只提供简单灵活的、无连接的、尽最大努力交付的数据报服务。 路由器在能够开始向输出链路传输分组的第一位之前&#xff0c;必须先接收到整个分组&#xff0c;这种机制称为&#xff1a;存储转发机制 异构网络互连 用…

transformers-Causal lanuage modeling

https://huggingface.co/docs/transformers/main/en/tasks/language_modelinghttps://huggingface.co/docs/transformers/main/en/tasks/language_modelingcausal lanuage model常用于文本生成。预测token系列中的下一个toekn&#xff0c;并且model只能关注左侧的token&#xf…

超声波清洗清洁力强怎么选、适合家用超声波清洗机推荐

因为各种原因很多导致很多小朋友从小就开始近视&#xff0c;佩戴眼镜&#xff0c;眼镜只要是戴上了就很难再摘下来&#xff0c;也有很多朋友从小到大都不知道清洗眼镜的重要性&#xff0c;眼镜长时间不清洗的话上面的细菌堪比茅厕这么脏&#xff01;所以眼镜清洗千万别忽视了&a…

vue2导出数据生成xlsx文件

1.在utils文件夹新建tool.js tool.js文件 import XEUtils from xe-utilsexport function exportCsv(csv, title) {const t XEUtils.toDateString(Date.now(), yyyy-MM-dd) // 当前日期const filename ${t title}.xlsx // 拼接文件名const blob new Blob([csv]) //创建一…

在Linux上编译gdal3.1.2指南

作者:朱金灿 来源:clever101的专栏 为什么大多数人学不会人工智能编程?>>> 以Ubuntu 18编译gdal3.1.2为例,编译gdal3.1.2需要先编译proj库和geos库(可选)。我选择的proj库版本为proj-7.1.0,编译proj-7.1.0需要先编译tiff库和sqlite3。我选择的sqlite3的版本为…

玩转多个数据库,一个Itbuilder在线工具就搞定!

随着需要使用的数据库类型日渐繁多&#xff0c;开发运维等技术人员如何高效便捷的访问、操作和管理数据&#xff0c;成了一个难题。设计一个好的数据库&#xff0c;就像孩子从小打下的基础&#xff0c;很多项目的失败是由于缺乏适当的数据库设计。因此&#xff0c;选择正确的数…