设计模式-责任链模式

2023-09-17 22:47:21

“单一职责原则”要求一个类仅负责的一个不可分业务逻辑,但这并不意味着能够实现这部分业务逻辑的只能有一个类,业务逻辑可能是会因运行时数据而选择不同类。比如在日常工作中,请假审批可能受请假天数、请假类型等因素影响,而须由不同领导来负责审批。再比如在银行取钱时,取钱业务审批申请可能会受到你所取钱总数、存储类型等因素影响,而须由不同经理或职员来负责办理。这一类事情其实在代码世界中更加常见,比如参数校验可能受到请求类型、请求数据的不同而由不同类(订单接口包括用户风控校验、限购校验、门店合法性、商品合法性等)来负责。这些案例都是同一套业务逻辑会由不同类型实现,那么如何更好的来完成功能执行过程呢?难道要每一个类全部都手动调一遍?之后增加其他类时还要修改大量的逻辑,非常不方便。这里的优化方案就是本文要讲述的责任链模式。

一、责任链模式概念

责任链模式的一般定义为:

使得每个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它位置。

从定义中看,责任链模式就是为了解决请求和多个需处理请求对象之间的耦合关系的,请求方不需要关心有哪些处理请求的对象,只需要感知这条“链”的存在即可。因此,责任链模式将多个对象处理请求的过程封装了起来,并且与请求方之间解耦。
后一句话指明了责任链中的对象负责的业务逻辑属于同一类(如请假审批),但不同对象责任存在差异(如主管审批与HR审批),在请求的过程中,只要在链上找到一个可以处理请求的对象即可。但是在日常开发中我们可能看到更多应用场景是所有对象都会处理请求(如用户风控校验、限购校验),不同对象之间处理请求并不会互相影响。后者在一些源码中应用于Filter、Handler等。
概念总结:

  • 责任链模式解决了请求和多个需处理请求对象之间的耦合关系
  • 责任链模式封装了多个对象处理请求的过程

二、应用实践

本章节给出一个关于“在线客服系统”的案例来说明下责任链模式。在线客服系统时,用户可以通过该系统向客服提出问题或请求帮助,其中由于公司业务扩展面会非常大,不同客服之间负责的方向存在很大的不提供。当用户提交问题,全部会由公司前台客服部门来负责接受请求。客服会在根据用户问题的难度和类型来判断是否自己是否可解决,否则应将问题抛给下一级客服部门,如业务部。依次类推,每一级客服都在这条责任链上,负责不同的任务,用户问题将会在这条责任链上流转,最终会流转到可以解决问题的部门客服。在上述案例中,设计到的对象可能有:

  • 前台客服:主要负责接听客户电话,处理一些常见问题及基本话术。
  • 业务客服:主要负责具体业务相关问题,比如产品设计、业务运营方面。
  • 技术客服:主要负责复杂技术远程排查问题能力
  • 售后客服:主要负责与用户实际沟通,解决现场问题。

  • 客服模版类:
public abstract class CustomerService {

    private CustomerService next;

    public void setNext(CustomerService next) {
        this.next = next;
    }

    public final void handleQuestion(Problem problem) {
        if (canHandle(problem)) {
            // 处理问题的逻辑
            System.out.println("您的问题我来解决~");
            doHandle(problem);
            return;
        }

        if(next == null) {
            throw new RuntimeException("无法处理相关问题");
        }

        next.handleQuestion(problem);   // 交给下一级客服处理
    }

    protected abstract boolean canHandle(Problem problem);

    protected abstract void doHandle(Problem problem);
}

前台客服:

public class FrontDeskCustomerService extends CustomerService {
    @Override
    protected boolean canHandle(Problem problem) {
        // 判断是否能处理问题的逻辑
        // 返回true表示能处理,返回false表示不能处理
        return false;
    }

    @Override
    protected void doHandle(Problem problem) {
        // 问题处理过程中...
    }
}

业务客服:

public class BusinessCustomerService extends CustomerService {
    @Override
    protected boolean canHandle(Problem problem) {
        // 判断是否能处理问题的逻辑
        // 返回true表示能处理,返回false表示不能处理
        return false;
    }

    @Override
    protected void doHandle(Problem problem) {
        // 问题处理过程中...
    }
}

客户端调用责任链:

public class Client {
    public static void main(String[] args) {
        // 创建客服对象
        CustomerService frontDesk = new FrontDeskCustomerService();
        CustomerService business = new BusinessCustomerService();
        CustomerService technical = new TechnicalCustomerService();
        CustomerService afterSales = new AfterSalesCustomerService();

        // 构建责任链
        frontDesk.setNext(business);
        business.setNext(technical);
        technical.setNext(afterSales);

        // 创建问题对象
        Problem problem = new Problem();

        // 处理问题
        frontDesk.handleQuestion(problem);
    }
}

在这里插入图片描述
在上述代码中,四种客服类通过继承抽象类CustomerService实现了责任链模式,Client客户端可通过FrontDeskCustomerService#handleQuestion方法直接获取问题的解决办法,而无需感知具体内部是哪个客服类回复的问题。这里注意CustomerService我们也使用了模版方法模式,所有的客服类都需重写canHandle()、doHandle()两个基本方法,责任链的判断、执行、转发流程都封装在handleQuestion()这个模版方法内部。基于这样的责任链设计,即使后续新增其他客服类,也只需要继承CustomerService并重写两个基本方法即可。
很明显,这种责任链模式的设计是不符合依赖倒置原则的,Client与客服模块没有通过抽象层产生依赖而是直接依赖了首个具体实现类。不符合依赖倒置原则,肯定也不符合开闭原则了,因为当FrontDeskCustomerService发生改变时,需要检查所有依赖其的客户端,影响巨大。
责任链模式的优点:

  • 责任链模式使得客户端(即请求方)与具体的处理过程之间解耦
  • 责任链模式封装了多个对象处理请求的过程
  • 逻辑清晰明了,非常容易新增和删除责任链中节点

责任链模式的缺点:

  • 责任链太长时,可能会影响整体性能
  • 责任链出现问题时,非常不利于排查问题
  • 责任链处理不当可能出现死循环、或无法处理等异常问题
  • 责任链不符合依赖倒置原则,不符合开闭原则。【向外暴露具体实现类不是个明智选择】

注:额外阅读材料

更多推荐

《时代》百大AI人物榜单公布,李彦宏、Sam Altman、黄仁勋等评为全球AI领袖

9月7日晚,《时代》周刊发布了首届全球百大AI人物。这100个人组成的群体在很多方面都是推动人工智能发展的关系和权力中心的地图。他们是竞争对手和监管者、科学家和艺术家、倡导者和高管——既竞争又合作的人类,他们的洞察力、欲望和缺陷将塑造这个影响力日益增强的技术的方向。”“人工智能的独特之处也是最令人恐惧和值得庆祝的地方,

生产消费者模型的介绍以及其的模拟实现

目录生产者消费者模型的概念生产者消费者模型的特点基于阻塞队列BlockingQueue的生产者消费者模型对基于阻塞队列BlockingQueue的生产者消费者模型的模拟实现ConProd.c文件的整体代码BlockQueue.h文件的整体代码对【基于阻塞队列BlockingQueue的生产者消费者模型的模拟实现】的测试

openGauss学习笔记-70 openGauss 数据库管理-创建和管理普通表-查看表数据

文章目录openGauss学习笔记-70openGauss数据库管理-创建和管理普通表-查看表数据70.1查询数据库所有表的信息70.2查询表的属性70.3查询表的数据量70.4查询表的所有数据70.5查询字段的数据70.6过滤字段的重复数据70.7查询字段为某某的所有数据70.8按照字段进行排序openGauss学习

深入学习 Redis Sentinel - 基于 DockerCompose 编排哨兵分布式架构,理解工作原理

目录一、哨兵模式1.1、为何引入哨兵模式1.2、RedisSentinel分布式架构1.2.1、概述1.2.2、工作原理(redis哨兵的核心功能)1.监控:2.自动故障转移:3.通知1.2.3、问题:哨兵结点只有一个可以么?1.3、使用Docker和DockerCompose模拟部署哨兵模式1.3.1、前言1.3.2

搭建ELK+Filebead+zookeeper+kafka实验

部署Zookeeper集群准备3台服务器做Zookeeper集群192.168.10.17192.168.10.21192.168.10.221.安装前准备关闭防火墙systemctlstopfirewalldsystemctldisablefirewalldsetenforce0安装JDKyuminstall-yja

Vulnhub实战-DC9

前言本次的实验靶场是Vulnhub上面的DC-9,其中的渗透测试过程比较多,最终的目的是要找到其中的flag。一、信息收集对目标网络进行扫描arp-scan-l对目标进行端口扫描nmap-sC-sV-oAdc-9192.168.1.131扫描出目标开放了22和80两个端口,访问目标的80端口。对目标进行目录扫描与分析。

第三十章 Classes - 方法生成器

[toc]第三十章Classes-方法生成器方法生成器方法生成器是类编译器在类编译期间调用的程序。它的输出是该方法的实际运行时实现。方法生成器提供了一种继承方法的方法,可以生成根据继承类或属性的需要定制的高性能、专用代码。在IRIS库中,方法生成器广泛用于数据类型和存储类。ClassQueries类可以包含类查询。类查

【自学开发之旅】Flask-会话保持-API授权-注册登录

http-无状态-无法记录是否已经登陆过#会话保持–sessioncookiesession–保存一些在服务端cookie–保存一些数据在客户端session在单独服务器D上保存,前面数个服务器A,B,C上去取就好了,业务解耦。—》》现在都是基于token的验证。以上是基于BS架构API授权由服务端完全把控三张表,ap

【Linux基础】第29讲 Linux用户和用户组权限控制命令(一)

1useradd添加新用户(注意:当前用户必须有添加用户的权限)1)基本语法useradd用户名(功能描述:添加新用户)2)案例root@sue-virtual-machine:/usr/local#useraddhadoop2passwd设置用户密码1)基本语法passwd用户名(功能描述:设置用户密码)2)案例ro

JavaScript与jQuery(下篇)

JavaScript与jQuery笔记(下篇)一、获取jquery二、jquery选择器三、jquery事件四、jquery操作Dom元素————————创作不易,如觉不错,随手点赞,关注,收藏(* ̄︶ ̄),谢谢~~jQueryjquery库,里面存在大量的javascript函数一、获取jqueryhttps://w

【1++的C++进阶】之特殊类设计

👍作者主页:进击的1++🤩专栏链接:【1++的C++进阶】文章目录一,设计一个类使其不能被拷贝二,设计一个类只能在堆上创建对象三,设计一个类只能在栈上创建对象四,设计一个类不能够被继承五,单例模式一,设计一个类使其不能被拷贝我们有三种方法能够设计此种类拷贝构造函数私有化拷贝构造函数只声明不定义拷贝构造函数后加del

热文推荐