责任链模式:将复杂逻辑轻松分解

写在前面

当谈到责任链模式时,我们常常会被其优雅的设计和灵活的应用所吸引。本文深入剖析了责任链模式的核心原理,并结合实战案例展示了如何在实际项目中应用责任链模式,轻松应对复杂的业务逻辑和任务处理流程。无论你是初学者还是有经验的开发者,这篇文章都将为你揭开责任链模式的神秘面纱,帮助你更好地理解和运用这一强大的设计模式,让你的代码拥有更强大的可扩展性和灵活性。如果你想要探寻责任链模式的无限潜力,那么这篇文章绝对不容错过!

什么是责任链模式

在一些业务场景下,为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。在责任链模式中,多个处理器(也就是刚刚定义中说的"接收对象")依次处理同一个请求。一个请求先经过A处理器处理,然后再把请求传递给B处理器,B处理器处理完后再传递给C处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作职责链模式。

责任链模式的核心原理

责任链模式的核心原理是将请求发送者和接收者解耦,形成一条由多个处理对象组成的处理链。当一个请求从发送者进入责任链时,每个处理对象都有机会处理该请求,直到其中某个处理对象能够完全处理请求或者将请求传递给下一个处理对象。

责任链模式的核心角色:

  1. 抽象处理者(Handler):定义一个处理请求的接口,通常包含一个指向下一个处理者的引用。
  2. 具体处理者(ConcreteHandler):实现抽象处理者接口,负责处理它所能处理的请求,如果自己不能处理,就将请求传递给下一个处理者。
  3. 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

责任链模式如何实现

这里以工厂的流水线生产为例,来分析研究一下责任链模式如何实现。

需求描述

假设一台手机的各种零部件已经制造完成,具体的手机的组装过程是这样的:

  1. 主板安装
  2. 电池安装
  3. 屏幕安装
  4. 通电测试

如果写一段程序来模拟这个组装过程,选择使用责任链模式来实现应该怎么实现呢?

实现方法

1、声明一个抽象的分步组装的处理类,定义抽象的组装方法和指向下一步的处理者的引用 ;

java 复制代码
/**
 * 抽象的分步组装处理类
 */
public abstract class AbstractHandler {
    private AbstractHandler abstractHandler;
    public AbstractHandler getAbstractHandler() {
        return abstractHandler;
    }
    public void next(AbstractHandler abstractHandler) {
        this.abstractHandler = abstractHandler;
    }
    public abstract void make();
}

2、声明具体的处理者,即主板安装的处理者,继承了抽象的分步组装处理器,实现抽象的组装方法,即主板的安装;

java 复制代码
/**
 * 主板安装
 */
public class MotherboardHandler extends AbstractHandler {
    @Override
    public void make() {
        System.out.println("主机安装完成");
        if (this.getAbstractHandler() != null) {
            this.getAbstractHandler().make();
        }
    }
}

3、声明第二具体的处理者,即电池安装的处理者,继承了抽象的分步组装处理器,实现抽象的组装方法,即电池的安装;

java 复制代码
/**
 * 电池安装
 */
public class BatteryHandler extends AbstractHandler{
    @Override
    public void make() {
        System.out.println("电池组装完成");
        if (this.getAbstractHandler() != null) {
            this.getAbstractHandler().make();
        }
    }
}

4、声明具体的处理者,即屏幕安装的处理者,继承了抽象的分步组装处理器,实现抽象的组装方法,即屏幕的安装;

java 复制代码
/**
 * 屏幕安装
 */
public class ScreenHandler extends AbstractHandler{
    @Override
    public void make() {
        System.out.println("屏幕安装完成");
        if (this.getAbstractHandler() != null) {
            this.getAbstractHandler().make();
        }
    }
}

5、声明具体的处理者,即整机上电测试的处理者,继承了抽象的分步组装处理器,实现抽象的组装方法,即上电测试;

java 复制代码
/**
 * 上电测试
 */
public class TestHandler extends AbstractHandler {
    @Override
    public void make() {
        System.out.println("上电测试通过");
        if (this.getAbstractHandler() != null) {
            this.getAbstractHandler().make();
        }
    }
}

6、声明一个客户端,也就是手机组装生产线,主要业务也就是把组装过程的每一步整合起来,这样一条完整的手机组装生产线就搭建好了,可以开始生产活动了;

java 复制代码
/**
 * 生产线
 */
public class ProductLine {
    public static void main(String[] args) {
        AbstractHandler step1=new MotherboardHandler();
        AbstractHandler step2=new BatteryHandler();
        AbstractHandler step3=new ScreenHandler();
        AbstractHandler step4=new TestHandler();
        step1.next(step2);
        step2.next(step3);
        step3.next(step4);
        step1.make();
    }
}

如何扩展

科技进步了,手机不仅能接电话和打电话,还能拍照,那么原来的手机组装生产线就需要升级,增加摄像头的组装。面对这样的扩展需求,应该怎么实现呢?很简单,往下看:

1、增加一个工位,即声明一个摄像头安装的处理类,同样继承于抽象的分步组装处理器,实现抽象的组装方法,即摄像头安装;

java 复制代码
/**
 * 报像头安装
 */
public class CameraHandler extends AbstractHandler{
    @Override
    public void make() {
        System.out.println("摄像头安装完成");
        if (this.getAbstractHandler() != null) {
            this.getAbstractHandler().make();
        }
    }
}

2、原来的生产线的工作顺序调整一下,在主机安装完成后,增加一步摄像头的组装,就可以了;原来的其他步骤的工作逻辑不变,完全符合开闭原则。

java 复制代码
/**
 * 生产线
 */
public class ProductLine {
    public static void main(String[] args) {
        AbstractHandler step1=new MotherboardHandler();
        AbstractHandler step1_2=new CameraHandler();
        AbstractHandler step2=new BatteryHandler();
        AbstractHandler step3=new ScreenHandler();
        AbstractHandler step4=new TestHandler();
        step1.next(step2);
        step1.next(step1_2);
        step1_2.next(step2);
        step2.next(step3);
        step3.next(step4);
        step1.make();
    }
}

责任链模式的适用场景

责任链模式适用于处理具有依次顺序、多个步骤的场景,如:

  1. 订单处理系统:在订单处理系统中,订单需要依次经过多个环节的处理,比如校验订单信息、生成订单号、计算订单金额、库存扣减等等。每个环节都有可能出现异常情况,需要进行相应的处理。使用责任链模式,可以将每个处理环节封装成一个处理器,通过责任链将订单依次传递给这些处理器进行处理,直到订单处理完成。这样可以使得订单处理流程更加清晰和灵活。
  2. 日志记录系统:在一个日志记录系统中,日志需要经过多个过滤器进行处理,比如按照日志级别进行过滤、按照关键字进行过滤、按照时间进行过滤等等。每个过滤器都有自己的处理逻辑和优先级,需要根据配置来确定日志经过哪些过滤器进行处理。使用责任链模式,可以将每个过滤器封装成一个处理器,通过责任链将日志依次传递给这些过滤器进行处理,直到所有过滤器都处理完毕。这样可以使得日志过滤流程更加灵活和可配置。
  3. 用户登录验证:手机验证码登录需要进行用户是否存在、验证码是否为空、验证码是否正确、用户是否锁定、用户是否被禁用等校验。这些校验都可以封装成处理器,通过责任链依次传递给这些处理器进行处理。

责任链模式在Spring中的应用

责任链模式在Spring中有很多应用场景:

  1. Spring的异常处理:Spring的异常处理机制采用了责任链模式,通过定义不同的异常处理器,将不同的异常类型传递给不同的处理器进行处理。这样可以使得异常处理更加灵活和可配置。
  2. Spring的拦截器:Spring的拦截器使用了责任链模式,通过定义不同的拦截器,将请求依次传递给这些拦截器进行处理。这样可以使得请求处理流程更加清晰和灵活。
  3. Spring的AOP实现:Spring的AOP实现也使用了责任链模式,通过定义不同的切面(Aspect),将请求依次传递给这些切面进行处理。这样可以使得代码更加清晰和易于维护。
  4. Spring的HandlerInterceptor:在Spring MVC框架中,可以使用HandlerInterceptor来拦截请求并处理请求。每个HandlerInterceptor都可以看作是一个处理器,通过责任链将请求依次传递给这些处理器进行处理。这样可以使得请求处理流程更加灵活和可配置。

总结

优点

  1. 降低对象之间的耦合度:责任链模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
  2. 增强系统的可扩展性:可以根据需要增加新的请求处理类,满足开闭原则。
  3. 增强给对象指派职责的灵活性:当工作流程发生变化时,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
  4. 简化对象之间的连接:每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的if或者if···else语句。
  5. 责任分担:每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

缺点

  1. 性能问题:每个请求都是从链表头遍历到链表尾,特别是链表比较长的时候,性能是一个非常大的问题。
  2. 调试不方便:特别是链表比较长,环节比较多的时候,由于采用了类似递归的方式,调试的时候可能逻辑比较复杂。

在使用责任链模式时,需要注意链中节点的数量需要控制,避免出现超长链的情况。一般做法是在Handler中设置一个最大的节点数量,在setNext()方法中判断是否已经是超过其阈值,超过则不允许建立,避免无意识的破坏系统性能。

相关推荐
禁默18 分钟前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Cachel wood25 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Code哈哈笑27 分钟前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
gb421528730 分钟前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶31 分钟前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
zfoo-framework38 分钟前
【jenkins插件】
java
风_流沙44 分钟前
java 对ElasticSearch数据库操作封装工具类(对你是否适用嘞)
java·数据库·elasticsearch
颜淡慕潇1 小时前
【K8S问题系列 |19 】如何解决 Pod 无法挂载 PVC问题
后端·云原生·容器·kubernetes
ProtonBase1 小时前
如何从 0 到 1 ,打造全新一代分布式数据架构
java·网络·数据库·数据仓库·分布式·云原生·架构