设计模式-责任链模式

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

一、责任链模式概念

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

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

从定义中看,责任链模式就是为了解决请求和多个需处理请求对象之间的耦合关系的,请求方不需要关心有哪些处理请求的对象,只需要感知这条"链"的存在即可。因此,责任链模式将多个对象处理请求的过程封装了起来,并且与请求方之间解耦。

后一句话指明了责任链中的对象负责的业务逻辑属于同一类(如请假审批),但不同对象责任存在差异(如主管审批与HR审批),在请求的过程中,只要在链上找到一个可以处理请求的对象即可。但是在日常开发中我们可能看到更多应用场景是所有对象都会处理请求(如用户风控校验、限购校验),不同对象之间处理请求并不会互相影响。后者在一些源码中应用于Filter、Handler等。

概念总结:

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

二、应用实践

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

  • 前台客服:主要负责接听客户电话,处理一些常见问题及基本话术。
  • 业务客服:主要负责具体业务相关问题,比如产品设计、业务运营方面。
  • 技术客服:主要负责复杂技术远程排查问题能力
  • 售后客服:主要负责与用户实际沟通,解决现场问题。
  • ...
    客服模版类:
java 复制代码
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);
}

前台客服:

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

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

业务客服:

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

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

客户端调用责任链:

java 复制代码
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发生改变时,需要检查所有依赖其的客户端,影响巨大。

责任链模式的优点:

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

责任链模式的缺点:

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

注:额外阅读材料

相关推荐
Chen-Edward1 小时前
有了Spring为什么还有要Spring Boot?
java·spring boot·spring
陈小桔2 小时前
idea中重新加载所有maven项目失败,但maven compile成功
java·maven
小学鸡!2 小时前
Spring Boot实现日志链路追踪
java·spring boot·后端
xiaogg36782 小时前
阿里云k8s1.33部署yaml和dockerfile配置文件
java·linux·kubernetes
逆光的July2 小时前
Hikari连接池
java
微风粼粼2 小时前
eclipse 导入javaweb项目,以及配置教程(傻瓜式教学)
java·ide·eclipse
番茄Salad2 小时前
Spring Boot临时解决循环依赖注入问题
java·spring boot·spring cloud
天若有情6733 小时前
Spring MVC文件上传与下载全面详解:从原理到实战
java·spring·mvc·springmvc·javaee·multipart
祈祷苍天赐我java之术3 小时前
Redis 数据类型与使用场景
java·开发语言·前端·redis·分布式·spring·bootstrap
Olrookie4 小时前
若依前后端分离版学习笔记(二十)——实现滑块验证码(vue3)
java·前端·笔记·后端·学习·vue·ruoyi