责任链模式

责任链模式

责任链模式是一种行为型设计模式。它的核心思想是:

  • 将一系列处理请求的对象连成一条链。
  • 请求沿着这条链传递,直到某个对象处理它。
  • 发送请求的对象不需要知道链上的具体处理者是谁。
  • 每个处理者只负责自己能处理的部分,不能处理则传递给下一个。

通俗理解:

想象一个审批流程,员工提交请假申请,首先交给直属主管审批,如果主管不处理或条件不满足,就自动传递到部门经理,再传递到人力资源审批,直到处理完成。

责任链模式解决的问题:

  1. 解耦发送者和接收者:发送请求的对象无需知道处理请求的具体类。
  2. 动态添加处理节点:链条可以在运行时动态构建。
  3. 可以自由选择处理顺序:责任链中的节点顺序可调整。
  4. 增强灵活性和扩展性:增加新的处理者节点,不会影响现有代码。

典型应用场景:

  • 审批流程(多级审批、请假申请、报销)
  • 日志过滤(INFO、WARN、ERROR)
  • 请求拦截(Servlet Filter、Spring Interceptor)
  • 事件处理(GUI事件传递、观察者链)

责任链一般都包含什么

  1. Handler(处理者抽象类)
    • 定义处理请求的接口和持有下一个处理者的引用。
    • 可以有一个 next 指针指向下一个节点。
    • 定义 handleRequest() 抽象方法,由具体子类实现。
  2. ConcreteHandler(具体处理者)
    • 继承 Handler,实现具体的处理逻辑。
    • 可以根据条件决定是否处理请求或传递给下一个节点。
  3. Client(客户端)
    • 构建责任链。
    • 发起请求,将请求传入链条的入口。
  4. Request(请求对象,可选)
    • 封装请求参数,传递给责任链节点。
    • 在你的示例里就是 AuthInfo 返回的审批信息,也可以是请求封装对象。

责任链结构图可以抽象如下:

复制代码
Client -> Handler1 -> Handler2 -> Handler3 -> ...
  • Client 不知道具体哪个 Handler 会处理请求。
  • 每个 Handler 自己判断能否处理,不能处理就交给 next。

责任链模式核心设计

  • 链条是动态可扩展的
    • 通过 appendNext 或者构造函数动态组合节点。
  • 节点内逻辑独立
    • 每个节点只关注自己处理的业务逻辑,不依赖其他节点。
  • 递归或循环传递请求
    • 节点处理完后,如果有下一个节点,递归调用下一节点处理请求。
  • 可停止链条
    • 如果某个节点处理完成并满足条件,可以选择不传递给下一个节点。
  • 支持返回结果
    • 可以返回处理结果,或者继续沿链传递。

示例

假设我们当前有一个需求就是设计层层审批 最简单的结果应该是

java 复制代码
public class AuthController {

    private SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 时间格式化

    public AuthInfo doAuth(String uId, String orderId, Date authDate) throws ParseException {

        // 三级审批
        Date date = AuthService.queryAuthInfo("1000013", orderId);
        if (null == date) return new AuthInfo("0001", "单号:", orderId, " 状态:待三级审批负责人 ", "王工");

        // 二级审批
        if (authDate.after(f.parse("2020-06-01 00:00:00")) && authDate.before(f.parse("2020-06-25 23:59:59"))) {
            date = AuthService.queryAuthInfo("1000012", orderId);
            if (null == date) return new AuthInfo("0001", "单号:", orderId, " 状态:待二级审批负责人 ", "张经理");
        }

        // 一级审批
        if (authDate.after(f.parse("2020-06-11 00:00:00")) && authDate.before(f.parse("2020-06-20 23:59:59"))) {
            date = AuthService.queryAuthInfo("1000011", orderId);
            if (null == date) return new AuthInfo("0001", "单号:", orderId, " 状态:待一级审批负责人 ", "段总");
        }

        return new AuthInfo("0001", "单号:", orderId, " 状态:审批完成");
    }

}

像是这样的功能看起来很简单的,但是实际的业务中会有很多部门,但如果这样实现就很难进行扩展,并且在改动扩展调整也非常麻烦。

上图是这个业务模型中责任链结构的核心部分,通过三个实现了统一抽象类AuthLink的不同规则,再进行责任编排模拟出一条链路。这个链路就是业务中的责任链。

一般在使用责任链时候如果是场景比较固定,可以通过写死到代码中进行初始化。但如果业务场景经常变化可以做成xml配置的方式进行处理,也可以落到库里进行初始化操作。

java 复制代码
public class AuthInfo {

    private String code;
    private String info = "";

    public AuthInfo(String code, String ...infos) {
        this.code = code;
        for (String str:infos){
            this.info = this.info.concat(str);
        }
    }
    
    // ...get/set
}//定义责任链模式

抽象类定义

java 复制代码
public abstract class AuthLink {

    protected Logger logger = LoggerFactory.getLogger(AuthLink.class);

    protected SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 时间格式化
    protected String levelUserId;                           // 级别人员ID
    protected String levelUserName;                         // 级别人员姓名
    private AuthLink next;                                  // 责任链

    public AuthLink(String levelUserId, String levelUserName) {
        this.levelUserId = levelUserId;
        this.levelUserName = levelUserName;
    }

    public AuthLink next() {
        return next;
    }

    public AuthLink appendNext(AuthLink next) {
        this.next = next;
        return this;
    }

    public abstract AuthInfo doAuth(String uId, String orderId, Date authDate);

}

为什么需要抽象类

责任链模式的设计思想:

  1. 每个节点只处理自己的逻辑
  2. 节点可以传递请求给下一个节点
  3. 链条是动态可扩展的

如果你直接写一个类让 Client 调接口,会出现几个问题:

  • 所有审批逻辑都写在同一个类里 → 违背单一职责原则

    例如一级、二级、三级审批都在同一个类里,你要处理修改审批逻辑就要改这个类,任何小改动都会影响整体。

  • 链条不灵活 → 增加新级别审批时必须改原类

    你如果想新增四级审批,就要修改已有类逻辑,破坏开闭原则。

  • 难以复用 → 不同业务场景不能复用节点

    你可能还想在其他流程里复用一级审批逻辑,如果所有逻辑都写在一个类里,你无法单独调用。


抽象类 AuthLink 的作用就是定义责任链节点的统一接口

  • 定义了 doAuth() 抽象方法,每个具体节点必须实现。
  • 持有 next 指针,实现责任链传递机制。
  • 提供 appendNext() 方法,让 Client 可以动态组合节点。

抽象类不是强制要求必须用,但它提供了统一规范

  • 任何节点都必须实现 doAuth()
  • 节点都可以使用 next 传递请求,无需每个节点重复实现。

如果你不用抽象类,而直接写一个类,每个节点就可能变成"写死"的方法:

java 复制代码
public class AuthService {
    public AuthInfo doLevel1() {...}
    public AuthInfo doLevel2() {...}
    public AuthInfo doLevel3() {...}
}

这就不是责任链了,而是顺序调用,失去了责任链的灵活性。


每个实现类 Level1AuthLinkLevel2AuthLinkLevel3AuthLink

  • 独立处理自己的业务逻辑

    每一级审批逻辑不同(时间段、判断条件、审批人),独立实现。

  • 可复用

    例如你在另一个流程也要用一级审批,只需实例化 Level1AuthLink

  • 可以灵活组合链条

    Client 可以决定是一级→二级→三级,还是一级→三级跳过二级。


假设你写一个单类接口:

java 复制代码
AuthInfo doAuth(String uId, String orderId, Date authDate);

然后内部写死了一级、二级、三级审批:

  • 链条无法动态调整顺序
  • 新增节点必须改原类
  • 节点间逻辑耦合度高
  • 复用性差

责任链模式强调的"链条动态传递 + 节点独立"就被破坏了

实现类定义

java 复制代码
public class Level1AuthLink extends AuthLink {

    public Level1AuthLink(String levelUserId, String levelUserName) {
        super(levelUserId, levelUserName);
    }

    public AuthInfo doAuth(String uId, String orderId, Date authDate) {
        Date date = AuthService.queryAuthInfo(levelUserId, orderId);
        if (null == date) {
            return new AuthInfo("0001", "单号:", orderId, " 状态:待一级审批负责人 ", levelUserName);
        }
        AuthLink next = super.next();
        if (null == next) {
            return new AuthInfo("0000", "单号:", orderId, " 状态:一级审批完成负责人", " 时间:", f.format(date), " 审批人:", levelUserName);
        }

        return next.doAuth(uId, orderId, authDate);
    }

}
java 复制代码
public class Level2AuthLink extends AuthLink {

    private Date beginDate = f.parse("2020-06-11 00:00:00");
    private Date endDate = f.parse("2020-06-20 23:59:59");

    public Level2AuthLink(String levelUserId, String levelUserName) throws ParseException {
        super(levelUserId, levelUserName);
    }

    public AuthInfo doAuth(String uId, String orderId, Date authDate) {
        Date date = AuthService.queryAuthInfo(levelUserId, orderId);
        if (null == date) {
            return new AuthInfo("0001", "单号:", orderId, " 状态:待二级审批负责人 ", levelUserName);
        }
        AuthLink next = super.next();
        if (null == next) {
            return new AuthInfo("0000", "单号:", orderId, " 状态:二级审批完成负责人", " 时间:", f.format(date), " 审批人:", levelUserName);
        }

        if (authDate.before(beginDate) || authDate.after(endDate)) {
            return new AuthInfo("0000", "单号:", orderId, " 状态:二级审批完成负责人", " 时间:", f.format(date), " 审批人:", levelUserName);
        }

        return next.doAuth(uId, orderId, authDate);
    }

}
java 复制代码
public class Level3AuthLink extends AuthLink {

    private Date beginDate = f.parse("2020-06-01 00:00:00");
    private Date endDate = f.parse("2020-06-25 23:59:59");

    public Level3AuthLink(String levelUserId, String levelUserName) throws ParseException {
        super(levelUserId, levelUserName);
    }

    public AuthInfo doAuth(String uId, String orderId, Date authDate) {
        Date date = AuthService.queryAuthInfo(levelUserId, orderId);
        if (null == date) {
            return new AuthInfo("0001", "单号:", orderId, " 状态:待三级审批负责人 ", levelUserName);
        }
        AuthLink next = super.next();
        if (null == next) {
            return new AuthInfo("0000", "单号:", orderId, " 状态:三级审批负责人完成", " 时间:", f.format(date), " 审批人:", levelUserName);
        }

        if (authDate.before(beginDate) || authDate.after(endDate)) {
            return new AuthInfo("0000", "单号:", orderId, " 状态:三级审批负责人完成", " 时间:", f.format(date), " 审批人:", levelUserName);
        }

        return next.doAuth(uId, orderId, authDate);
    }

}
  • 如上三个类;Level1AuthLinkLevel2AuthLinkLevel3AuthLink,实现了不同的审核级别处理的简单逻辑。
  • 例如第一个审核类中会先判断是否审核通过,如果没有审核通过则返回结果给调用方,引导去审核。(这里简单模拟审核后有时间信息不为空,作为判断条件)
  • 判断完成后获取下一个审核节点;super.next();,如果不存在下一个节点,则直接返回结果。
  • 之后是根据不同的业务时间段进行判断是否需要,二级和一级的审核。
  • 最后返回下一个审核结果;next.doAuth(uId, orderId, authDate);,有点像递归调用
相关推荐
明洞日记23 分钟前
【设计模式手册012】责任链模式 - 请求处理的流水线艺术
java·设计模式·责任链模式
q***071427 分钟前
Java实战:Spring Boot application.yml配置文件详解
java·网络·spring boot
雨中飘荡的记忆38 分钟前
Spring Alibaba AI 实战指南
java·ai编程
冰封剑心40 分钟前
MiniCPM-V-2_6 (4-bit 量化)使用
java·前端·数据库
mqiqe42 分钟前
【Spring AI MCP】四、MCP 服务端
java·人工智能·spring
l***74941 小时前
springboot与springcloud对应版本
java·spring boot·spring cloud
稚辉君.MCA_P8_Java1 小时前
Gemini永久会员 Java实现的暴力递归版本
java·数据结构·算法
许商1 小时前
【stm32】【printf】
java·前端·stm32
JIngJaneIL1 小时前
智慧物业|物业管理|基于SprinBoot+vue的智慧物业管理系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·论文·智慧物业管理系统