首页 > 其他分享 >责任链模式:如何解决审核、过滤场景问题?

责任链模式:如何解决审核、过滤场景问题?

时间:2024-09-29 16:21:32浏览次数:8  
标签:场景 处理 void next 过滤 println 审核 public out

相较而言,责任链模式是一个使用频率很高的模式,大家在日常的开发过程中,也可能会经常遇到,下面,我们就一起来了解一下责任链模式的内容。

一、模式原理分析

责任链模式的原始定义是:通过为多个对象提供处理请求的机会,避免将请求的发送者与其接收者耦合。链接接收对象并沿着链传递请求,直到对象处理它。

这个定义读起来还是有点抽象难懂,实际上它只说了一个关键点:通过构建一个处理流水线来对一次请求进行多次的处理

这里我们结合购物的例子来解释下:当你收到了购买的商品后,发现商品有质量问题,于是你打电话询问客服关于退货的流程,客服接到你的电话后,会先打开订单系统查询你提供的订单信息并确认是否正确,确认后再使用物流系统通知快递小哥上门取件,快递小哥取件后会返回商品让仓储系统进行确认,并通知商品系统……这样的一个过程就是责任链模式的真实应用。

那么,我们先来看看责任链模式的 UML 图:

从该 UML 图中,我们能看出责任链模式其实只有两个关键角色。

  • 处理类(Handler):可以是一个接口,用于接收请求并将请求分派到处理程序链条中(实际上就是一个数组链表),其中,会先将链中的第一个处理程序放入开头来处理。

  • 具体处理类(HandlerA、B、C):按照链条顺序对请求进行具体处理。

下面我们再来看看该 UML 对应的代码实现:

public interface Handler {
    void setNext(Handler handler);
    void handle(Request request);
}
public class Request {
    private String data;
    public String getData() {
        return data;
    }
    public void setData(String data) {
        this.data = data;
    }
}
public class HandlerA implements Handler{
    private Handler next;
    public HandlerA() {
    }
    @Override
    public void setNext(Handler handler) {
        this.next = handler;
    }
    @Override
    public void handle(Request request) {
        System.out.println("HandlerA 执行 代码逻辑,处理:"+request.getData());
        request.setData(request.getData().replace("AB",""));
        if (null != next) {
            next.handle(request);
        } else {
            System.out.println("执行中止!");
        }
    }
}
public class HandlerB implements Handler {
    private Handler next;
    public HandlerB() {
    }
    @Override
    public void setNext(Handler handler) {
        this.next = handler;
    }
    @Override
    public void handle(Request request) {
        System.out.println("HandlerB 执行 代码逻辑,处理:"+request.getData());
        request.setData(request.getData().replace("CD",""));
        if (null != next) {
            next.handle(request);
        } else {
            System.out.println("执行中止!");
        }
    }
}
public class HandlerC implements Handler{
    private Handler next;
    public HandlerC() {
    }
    @Override
    public void setNext(Handler handler) {
        this.next = handler;
    }
    @Override
    public void handle(Request request) {
        System.out.println("HandlerC 执行 代码逻辑,处理:"+request.getData());
        if (null != next) {
            next.handle(request);
        } else {
            System.out.println("执行中止!");
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        Handler h1 = new HandlerA();
        Handler h2 = new HandlerB();
        Handler h3 = new HandlerC();
        h1.setNext(h2);
        h2.setNext(h3);
        Request request = new Request();
        request.setData("请求数据ABCDE");
        h1.handle(request);
    }
}
//输出结果
HandlerA 执行 代码逻辑,处理:请求数据ABCDE
HandlerB 执行 代码逻辑,处理:请求数据CDE
HandlerC 执行 代码逻辑,处理:请求数据E
执行中止!

从这段代码实现可以看出,责任链模式的实现非常简单,每一个具体的处理类都会保存在它之后的下一个处理类。当处理完成后,就会调用设置好的下一个处理类,直到最后一个处理类不再设置下一个处理类,这时处理链条全部完成。在代码示例中,HandlerA 删除掉字符串 ABCDE 中的 AB,并交给 HandlerB 处理;HandlerB 删除掉 CDE 中的 CD,并交给 HandlerC;HandlerC 处理完后,整个执行过程中止。

二、使用场景分析

责任链模式常见的使用场景有以下几种情况。

  • 在运行时需要动态使用多个关联对象来处理同一次请求时。比如,请假流程、员工入职流程、编译打包发布上线流程等。

  • 不想让使用者知道具体的处理逻辑时。比如,做权限校验的登录拦截器。

  • 需要动态更换处理对象时。比如,工单处理系统、网关 API 过滤规则系统等。

为了更好地理解责任链模式的使用场景,下面我们通过一个简单的例子来演示。

这里我们要创建一个获取数字并判断正负或零的程序,程序接收一个数字的请求,在链条上进行处理并打印对应的处理结果。

我们先创建一个链条 Chain,并设置起始的处理类,如下代码所示:

public class Chain {
    Excutor chain;
    public Chain(){
        buildChain();
    }
    private void buildChain(){
        Excutor e1 = new NegativeExcutor();
        Excutor e2 = new ZeroExcutor();
        Excutor e3 = new PositiveExcutor();
        e1.setNext(e2);
        e2.setNext(e3);
        this.chain = e1;
    }
    public void process(Integer num) {
        chain.handle(num);
    }
}

接下来我们创建抽象的处理类 Excutor,声明两个方法:setNext 用于设置下一个处理类,handle 是具体的业务逻辑。

public interface Excutor {
    void setNext(Excutor excutor);
    void handle(Integer num);
}

NegativeExcutor、PositiveExcutor 和 ZeroExcutor 分别代表处理负数、正数和零。

public class NegativeExcutor implements Excutor {
    private Excutor next;
    @Override
    public void setNext(Excutor excutor) {
        this.next = excutor;
    }
    @Override
    public void handle(Integer num) {
        if (null!= num && num < 0) {
            System.out.println("NegativeExcutor获取数字:"+num+" ,处理完成!");
        } else {
            if (null != next) {
                System.out.println("===经过NegativeExcutor");
                next.handle(num);
            } else {
                System.out.println("处理中止!-NegativeExcutor");
            }
        }
    }
}
public class PositiveExcutor implements Excutor{
    private Excutor next;
    @Override
    public void setNext(Excutor excutor) {
        this.next = excutor;
    }
    @Override
    public void handle(Integer num) {
        if (null!= num && num > 0) {
            System.out.println("PositiveExcutor获取数字:"+num+" ,处理完成!");
        } else {
            if (null != next) {
                System.out.println("===经过PositiveExcutor");
                next.handle(num);
            } else {
                System.out.println("处理中止!-PositiveExcutor");
            }
        }
    }
}
public class ZeroExcutor implements Excutor{
    private Excutor next;
    @Override
    public void setNext(Excutor excutor) {
        this.next = excutor;
    }
    @Override
    public void handle(Integer num) {
        if (null!= num && num == 0) {
            System.out.println("ZeroExcutor获取数字:"+num+" ,处理完成!");
        } else {
            if (null != next) {
                System.out.println("===经过ZeroExcutor");
                next.handle(num);
            } else {
                System.out.println("处理中止!- ZeroExcutor");
            }
        }
    }
}

最后,运行一个单元测试:

public class Client {
    public static void main(String[] args) {
        Chain chain = new Chain();
        chain.process(99);
        System.out.println("------");
        chain.process(-11);
        System.out.println("------");
        chain.process(0);
        System.out.println("------");
        chain.process(null);
    }
}
//输出结果
===经过NegativeExcutor
===经过ZeroExcutor
PositiveExcutor获取数字:99 ,处理完成!
------
NegativeExcutor获取数字:-11 ,处理完成!
------
===经过NegativeExcutor
ZeroExcutor获取数字:0 ,处理完成!
------
===经过NegativeExcutor
===经过ZeroExcutor
处理中止!-PositiveExcutor

从最后的结果可以看到,当我们输入不同的数时,都会经过一整个链条的流转,直到最终的处理对象完成处理。

所以说,责任链模式就像工厂的流水线作业一样,按照某一个标准化的流程来执行,用于规则过滤、Web 请求协议解析等具备链条式的场景中,通过拆分不同的处理节点来完成整个流程的处理。

三、为什么使用责任链模式?

分析完责任链模式的原理和使用场景后,我们再来说说使用责任链模式的原因,可总结为以下三个。

  • 第一个,解耦使用者和后台庞大的流程化处理。我们都知道,在线购物订单里包含了物流、商品、支付、会员等多个系统的处理逻辑,如果让使用者一一和它们对接,势必会造成使用困难、系统之间调用混乱的情况发生,而通过订单建立一个订单的状态变更流程,就能将这些系统很好地串联在一起,这不仅能够让使用者只需要关注订单流程这一个入口,同时还能够让不同的系统按照各自的职责来发挥作用。比如,订单在未完成支付前,商品系统是不会通知物流系统进行商品发货的。

  • 第二个,为了动态更换流程处理中的处理对象。比如,在请假流程中,申请人一般会提交申请给直接领导审批,但有时直接领导可能无法进行审批操作,这时系统就可以更换审批人到其他审批人,这样就不会阻塞请假流程的审批。

  • 第三个,为了处理一些需要递归遍历的对象列表。比如,权限的规则过滤。对于不同部门不同级别人员的权限,就可以采用一个过滤链条来进行权限的管控。

四、责任链模式的优缺点是什么?

通过上述分析,我们就可以总结出使用责任链模式的优点。

  • 降低客户端对象与处理链条上对象之间的耦合度。比如,提交上线审核,提交人只知道最开始申请的处理人是谁,而后续是否需要别的审核人其实是由处理链条来控制的。

  • 提升系统扩展性。对于需要多次处理的同一个请求,可以在链条上增加新的具体处理类,满足开闭原则,能极大地提升系统扩展性。

  • 增强了具体处理类的职责独立性。即便链条上的工作流程发生了变化,也可以动态地改变具体处理类的调用次序和增加类的新的职责。每个类只需要处理自己该处理的工作,不该处理的就传递给下一个对象完成,明确各类的责任范围,同时也符合类的单一职责原则。

  • 简化了对象之间前后关联处理的复杂性。每个对象只需存储一个指向后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。

同样,责任链模式也有一些缺点。

  • 降低性能。由于每一个请求都需要经历一次完整的链条上具体处理类的处理,系统性能势必会受到一定影响,比如,依赖更多的代码行或依赖更复杂的代码逻辑。

  • 调试难度增大。调试代码需要验证每个具体处理者是否都能接收到请求,一旦出现错误,排查与修改也变得更加麻烦。

  • 容易出现死锁异常。一旦某一个对象设置后继者出现错误,就会出现循环调用,进而导致堆栈溢出的错误。

在实际的软件开发中,责任链模式的应用非常广泛,可以说只要是与流程相关的软件系统都能够使用责任链模式来构建,一方面可以用在代码中实现松散耦合,另一方面可以动态增删子处理流程。

责任链模式的原理和实现虽然都非常简单,但是在实际使用中还需要注意维护上下文关系的正确性,一旦出现循环调用,很容易死锁而导致程序崩溃。

另外,要注意控制责任链中的处理对象数量。如果处理对象的数量过多,比如超过 20个,容易让代码变得难以维护,这时还是应该尽可能减少处理对象的数量,将其合并到相类似的处理对象中去。

文章(专栏)将持续更新,欢迎关注公众号:服务端技术精选。欢迎点赞、关注、转发

个人小工具程序上线啦,通过公众号菜单即可体验,欢迎大家体验后提出优化意见

标签:场景,处理,void,next,过滤,println,审核,public,out
From: https://blog.51cto.com/jiangyi/12146019

相关文章

  • 结婚证识别-离婚证识别接口-结婚证识别API应用场景
    在信息化与智能化高速发展的今天,证件的自动识别技术逐渐成为了各行各业数字化转型的关键工具,而结婚证识别接口、离婚证识别接口正在悄然改变着传统的民政工作方式。结婚证识别与离婚证识别接口是基于光学字符识别(OCR)技术的智能解决方案。通过这一接口,用户可以轻松实现对......
  • 基于nodejs+vue协同过滤音乐网站[开题+源码+程序+论文]计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容研究背景随着数字音乐产业的蓬勃发展,音乐网站已成为人们日常生活中不可或缺的一部分。然而,面对海量的音乐资源,如何高效、精准地为用户推荐符合其个人喜好的音乐成为......
  • Redis 五大基本数据类型及其应用场景进阶(缓存预热、雪崩 、穿透 、击穿)
    Redis数据类型及其应用场景Redis是什么?Redis是一个使用C语言编写的高性能的基于内存的非关系型数据库,基于Key/Value结构存储数据,通常用来缓解高并发场景下对某一资源的频繁请求,减轻数据库的压力。它支持多种数据类型,如字符串、哈希、列表、集合、有序集合等。Redis......
  • java基于协同过滤算法的springboot的煤矿员工健康管理系统(源码+文档+调试+vue+前后端
    收藏关注不迷路!!......
  • [场景设计]断点续传
    要实现大文件的断点续传,通常的实现方式是将文件分块上传(切割文件)并记录每个块的状态,以便在中断后可以从上次上传完成的块继续上传。你可以基于以下几个步骤来实现这个功能,主要涉及字节流操作、文件分块、状态记录和续传的逻辑。1.文件分块将大文件切割成多个小块进行上传,这样在......
  • FFmpeg 初学者需要掌握的基础知识和实用技能。每个部分可以深入讲解,提供具体的命令示
    FFmpeg初级使用教程大纲1. FFmpeg简介什么是FFmpegFFmpeg的主要功能安装FFmpeg2. 基本命令格式FFmpeg的基本命令结构输入与输出文件的指定常用选项的介绍3. 常用命令示例转换视频格式示例:将MP4转换为AVI提取音频示例:从视频中提取音频压缩视......
  • Redis 和 CDN 的应用场景区别及 Redis 大 Key 解析
    在现代互联网架构中,Redis和CDN都是非常重要的技术工具,它们在不同的应用场景中发挥着关键作用。 一、Redis的应用场景 (一)缓存场景 1. 加速数据访问:在Web应用中,Redis常被用来缓存频繁访问的数据,如数据库查询结果、页面片段等。以电商网站为例,热门商品的信息被缓......
  • 如何让大模型更好地进行场景落地?【文末送书】
    自ChatGPT模型问世后,在全球范围内掀起了AI新浪潮。有很多企业和高校也随之开源了一些效果优异的大模型,例如:Qwen系列模型、MiniCPM序列模型、Yi系列模型、ChatGLM系列模型、Llama系列模型、Baichuan系列模型、Deepseek系列模型、Moss模型等。图片来自:ASurveyofLargeLa......
  • 二进制、八进制、十进制、十六进制使用场景
    在编程和计算机系统中,二进制、八进制、十进制、十六进制都是用于表示数值的不同进制系统。每种进制在不同的场景中有其特定的应用。以下是它们的常见使用场景及原因:1.二进制(Binary,基数2)表示形式:由0和1组成,通常以0b或b为前缀表示(例如:0b1010表示十进制中的10)。使用场景:......
  • springcloud的gateway使用全局过滤器
    全局过滤器是可以做一些统一的事情,比如认证鉴权、日志处理等@ComponentpublicclassLogFilterimplementsGlobalFilter,Ordered{Loggerlog=LoggerFactory.getLogger(this.getClass());@OverridepublicMono<Void>filter(ServerWebExchangeexchange,G......