责任链模式实战:从if-else到优雅的校验链
2026/9/7 14:57:57 网站建设 项目流程

相信不少开发者在做审批流、敏感词过滤、参数校验这类功能时,都会遇到同一个问题:逻辑写多了以后,if-else越摞越长,新增一个处理规则就要改动原有方法,测试范围跟着变大,代码也越来越难维护。

我第一次接触责任链模式,是在一个风控需求里:一笔订单要经过黑名单校验、频次校验、金额校验、设备校验,最开始是四个if嵌套,后来变成六个,再后来领导说“再加一个积分校验”,我看着那几百行的if-else陷入了沉思。

后来重构成了责任链模式,问题一下子清爽了很多:每个校验规则独立成一个类,新增规则就是在链上多挂一个节点,不改原逻辑,不污染老代码。这篇文章就把责任链模式从概念到实战完整拆一遍,内容包括:模式的核心思想、UML 结构、两种实现方式、典型应用场景、完整可运行的 Java 示例、常见坑点以及工程落地建议。

如果你是刚学设计模式的新手,这篇文章可以帮你把概念吃透;如果你是写业务的老手,可以直接跳到第四节的实战代码和第五节的踩坑清单。

1. 责任链模式是什么

1.1 先从生活场景理解

责任链模式在生活中很常见。比如你提交了一个请假申请,流程是这样的:

  • 请假 1 天以内:直属主管审批即可。
  • 请假 3 天以内:主管审批后,需要经理审批。
  • 请假 3 天以上:主管、经理审批后,还需要总监审批。

当你提交申请时,这个请求会沿着一串审批人向后传递,每个审批人只判断“我能不能处理、我需不需要处理”,如果自己能处理就处理掉,不能处理就传给下一个人。这就是一条“责任链”。

再比如公司里的报销流程、OA 审批流、客服工单升级机制,本质上都是责任链模式的现实映射。

1.2 专业定义

责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式。它把多个处理者(Handler)串成一条链,请求沿着这条链依次传递,直到某个处理者决定处理该请求,或者链上所有处理者都无法处理为止。

这个模式的核心思想是:请求的发送者和接收者解耦。发送者不需要知道谁会处理这个请求,也不需要知道链上有多少处理者,它只需要把请求丢到链头即可。

用 GoF 原书的话说:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

1.3 责任链模式的四个角色

责任链模式包含以下核心角色:

角色说明
Handler(抽象处理者)定义处理请求的接口,通常包含一个设置后继处理者的方法和一个处理请求的方法
ConcreteHandler(具体处理者)实现抽象处理者,自己处理不了就把请求传给下一个
Client(客户端)组装责任链,并向链头的具体处理者提交请求
Request(请求对象)封装需要被处理的数据和上下文

1.4 两种实现流派:链表式与集合式

责任链模式在落地时通常有两大类实现:

  1. 链表式:每个处理者里面维护一个next字段,指向下一个处理者,请求沿着next指针逐级传递。这是最经典的实现方式,对应 GoF 原书的结构。
  2. 集合式:用一个List<Handler>保存所有处理者,客户端遍历列表依次调用。这种方式更贴近日常业务开发,配合 Spring 容器很好用。

两种方式没有绝对优劣。链表式严格符合模式定义,顺序在组装时确定;集合式更直观,配合注解和自动注入非常方便,但顺序控制需要额外规则。本文后面会分别给出示例。

2. 责任链模式解决什么问题

2.1 从嵌套 if-else 说起

先看一个非常常见的坏味道代码。假设我们写一个订单创建接口,需要做一系列前置校验:

public String createOrder(OrderRequest request) { // 1. 参数非空校验 if (request == null || request.getUserId() == null) { return "参数不合法"; } // 2. 用户状态校验 User user = userService.getUser(request.getUserId()); if (user == null) { return "用户不存在"; } if (user.getStatus() != 1) { return "用户已被禁用"; } // 3. 库存校验 Stock stock = stockService.getStock(request.getSkuId()); if (stock == null || stock.getCount() < request.getCount()) { return "库存不足"; } // 4. 风控校验 if (!riskService.pass(request)) { return "订单疑似风险,请重试"; } // 5. 优惠券校验 if (couponService.isExpired(request.getCouponId())) { return "优惠券已过期"; } // 6. 真正创建订单 return orderService.create(request); }

这段代码的问题很明显:

  • 耦合重:校验逻辑、业务规则全部堆在创建订单的方法里。
  • 扩展难:每次新增一个校验规则,都要改createOrder方法,违背开闭原则。
  • 可读性差:方法越来越长,排查问题要在代码里来回找。
  • 不易复用:别的接口可能也需要一样的用户校验、风控校验,但复制粘贴会造成大量重复代码。

用责任链模式改造后,每个校验规则独立成一个处理器,主流程只负责组装链和启动链:

public String createOrder(OrderRequest request) { return orderValidateChain.execute(request); }

主流程立刻变得非常干净,后面的章节我会给出完整的改造代码。

2.2 责任链模式的优点

引入责任链模式后,优点是非常明显的:

  1. 降低耦合度:发送方不需要知道链的具体结构,处理方之间也互不依赖。
  2. 符合开闭原则:新增处理者时,只需要写一个新类并接入链中,不用修改已有处理器。
  3. 灵活调整顺序:链路顺序在组装阶段决定,想改变规则先后顺序,调整组装代码即可。
  4. 增强职责分配的灵活性:可以动态决定一个请求由谁处理、走到哪一步终止。

2.3 责任链模式的缺点

缺点同样需要说清楚,不能光夸:

  1. 不能保证请求一定被处理:如果链上没有任何节点能处理,请求会默默丢失。这就需要在链尾加一个兜底处理器。
  2. 调试相对麻烦:请求在多个类之间传递,排查问题时需要顺着链路一个个看,不如单个方法直观。
  3. 类数量增加:每个处理规则一个类,处理器过多时类数量会膨胀。
  4. 性能损耗:请求要逐级传递,如果链路很长,会带来一定的调用开销。对绝大多数业务场景来说损耗可忽略,但在高频调用链路上要控制处理器数量。

3. 责任链模式的经典结构与最小实现

3.1 类结构设计

下面用 Java 展示责任链模式的经典结构,包含抽象处理者、两个具体处理者、请求对象和客户端。

先定义请求对象:

// 文件路径:com/example/chain/LeaveRequest.java public class LeaveRequest { private String name; // 请假人 private int days; // 请假天数 private String reason; // 请假原因 public LeaveRequest(String name, int days, String reason) { this.name = name; this.days = days; this.reason = reason; } public String getName() { return name; } public int getDays() { return days; } public String getReason() { return reason; } }

定义抽象处理者:

// 文件路径:com/example/chain/Approver.java public abstract class Approver { // 下一个处理者 protected Approver nextApprover; // 设置下一个处理者,返回下一个处理者便于链式调用 public Approver setNext(Approver nextApprover) { this.nextApprover = nextApprover; return this.nextApprover; } // 由子类实现具体的审批逻辑 public abstract void approve(LeaveRequest request); }

这里setNext方法返回nextApprover对象,是为了支持链式组装,写起来更简洁。具体处理者实现如下。

主管审批:

// 文件路径:com/example/chain/ManagerApprover.java public class ManagerApprover extends Approver { @Override public void approve(LeaveRequest request) { if (request.getDays() <= 3) { System.out.println("主管审批通过:" + request.getName() + " 请假 " + request.getDays() + " 天,原因:" + request.getReason()); return; } // 超出自己权限,交给下一个审批人 if (nextApprover != null) { nextApprover.approve(request); } else { System.out.println("当前无更高审批人,请求被挂起"); } } }

总监审批:

// 文件路径:com/example/chain/DirectorApprover.java public class DirectorApprover extends Approver { @Override public void approve(LeaveRequest request) { if (request.getDays() <= 7) { System.out.println("总监审批通过:" + request.getName() + " 请假 " + request.getDays() + " 天,原因:" + request.getReason()); return; } if (nextApprover != null) { nextApprover.approve(request); } else { System.out.println("当前无更高审批人,请求被挂起"); } } }

客户端组装链:

// 文件路径:com/example/chain/Client.java public class Client { public static void main(String[] args) { // 1. 创建处理者 Approver manager = new ManagerApprover(); Approver director = new DirectorApprover(); Approver ceo = new CeoApprover(); // 2. 组装责任链:主管 -> 总监 -> CEO manager.setNext(director); director.setNext(ceo); // 3. 提交请求 LeaveRequest request = new LeaveRequest("张三", 10, "婚假"); manager.approve(request); } }

当然,这里还需要一个CeoApprover,不过我想你已经理解写法了:仿照前面的类再实现一个就行。

3.2 链表式实现的关键要点

从上面代码可以看出,链表式实现有几个关键设计点:

  1. 幂等的处理判断:每个处理者内部有明确判断条件,自己能处理就处理,不能处理就传递。
  2. 统一的传递入口approve()方法内部必须包含“处理”和“传递”两条路径。
  3. 链尾兜底:最后一定要判断nextApprover == null,否则会空指针,或者请求悬空。

这种实现最贴合设计模式原意,也是面试中最常见的写法。

3.3 集合式实现(更贴近业务)

实际业务开发中,链表式写起来还是有点啰嗦。更常见的做法是结合List来处理。先定义一个统一接口:

// 文件路径:com/example/chain2/OrderHandler.java public interface OrderHandler { // 执行校验,返回 null 表示通过,返回字符串表示拦截原因 String handle(OrderRequest request); }

再定义一组处理器,每个处理器只负责一件事。客户端用一个List把它们串起来:

// 文件路径:com/example/chain2/OrderValidateChain.java public class OrderValidateChain { private final List<OrderHandler> handlerList; public OrderValidateChain(List<OrderHandler> handlerList) { this.handlerList = handlerList; } public String execute(OrderRequest request) { if (request == null) { return "请求参数不能为空"; } for (OrderHandler handler : handlerList) { String result = handler.handle(request); // 只要有一个处理器返回非空,说明请求被拦截 if (result != null) { return result; } } return null; } }

集合式实现的核心区别在于:链的传递不是由处理器自己调next,而是由客户端循环驱动。这样每个处理器不需要持有下一个处理器的引用,职责更单一,也更容易用 Spring 管理。

4. 责任链模式典型应用场景

责任链模式在开源框架和业务代码中都很常见,下面按场景分类说明。

4.1 框架中的责任链

大部分 Java 开发者其实早就用过责任链模式,只是当时没有意识到:

框架/组件责任链体现
Servlet Filter过滤器链,请求依次经过多个 Filter
Spring MVC Interceptor拦截器链,前置处理、后置处理依次执行
MyBatis Plugin插件通过动态代理形成拦截器链
Netty ChannelPipeline入站/出站事件在 pipeline 中逐节点传播
Dubbo Filter服务调用链上的过滤器链
Spring SecurityFilterChain 决定请求能否进入应用

以过滤器链为例,一个 HTTP 请求会依次经过编码过滤器、认证过滤器、授权过滤器、日志过滤器,最后才到达 Controller。这本质上就是一条责任链。

4.2 业务开发中的典型场景

业务代码里,责任链模式适合以下场景:

  1. 多层审批流:请假审批、报销审批、合同审批,不同层级的处理者对应不同审批权限。
  2. 多条件校验链:下单校验、注册校验、发布内容校验,多个校验规则串行执行,任一规则不通过就终止。
  3. 敏感词过滤:敏感词有多套词库、多个过滤策略,可以组成过滤链。
  4. 数据清洗流程:ETL 过程中,数据要经过去重、格式转换、非法数据剔除、补全等步骤。
  5. 消息消费前处理:消息到达后要经过幂等校验、黑名单校验、限流校验、业务处理。
  6. 风控规则引擎:不同风控规则组合成链,任何一个规则命中就触发拦截或人工审核。

4.3 什么时候不适合责任链

有经验的开发者都知道,设计模式不能乱用。以下情况就不建议用责任链:

  1. 只有一两个校验规则:直接if-else更直观,硬套模式反而增加类和间接层。
  2. 处理者之间有强依赖关系:如果第二个处理器的输入依赖第一个处理器的输出且关系复杂,责任链会很难维护。
  3. 需要大量回传和上下文状态:责任链适合“线性、单向”的流程,如果请求需要反复折返,就不合适了。

5. 实战案例:用责任链模式重构订单校验流程

下面用一个完整的订单校验案例,演示责任链模式在真实业务中怎么落地。我选用集合式实现,因为这个写法最容易集成到 Spring Boot 项目中。

5.1 需求背景

假设订单创建接口需要以下校验:

  1. 参数非空校验:订单请求不能为空,用户 ID 不能为空。
  2. 用户状态校验:用户必须存在且状态正常。
  3. 库存校验:商品库存必须充足。
  4. 风控校验:订单不能命中风控策略。
  5. 优惠券校验:优惠券不能已过期。

5.2 项目结构

src/main/java/com/example/chaindemo/ ├── ChainDemoApplication.java ├── controller/OrderController.java ├── request/OrderRequest.java ├── handler/OrderHandler.java ├── handler/ParamValidHandler.java ├── handler/UserValidHandler.java ├── handler/StockValidHandler.java ├── handler/RiskValidHandler.java ├── handler/CouponValidHandler.java ├── chain/OrderValidateChain.java └── service/OrderService.java

5.3 定义请求对象

// 文件路径:src/main/java/com/example/chaindemo/request/OrderRequest.java public class OrderRequest { private Long userId; private Long skuId; private Integer count; private String couponId; // 省略 getter/setter,实际代码需要补全 }

5.4 定义处理器接口

// 文件路径:src/main/java/com/example/chaindemo/handler/OrderHandler.java public interface OrderHandler { /** * 处理订单校验逻辑 * * @param request 订单请求 * @return null 表示校验通过;非 null 表示校验不通过,返回提示信息 */ String handle(OrderRequest request); }

5.5 实现具体处理器

参数校验处理器:

// 文件路径:src/main/java/com/example/chaindemo/handler/ParamValidHandler.java @Component public class ParamValidHandler implements OrderHandler { @Override public String handle(OrderRequest request) { if (request == null) { return "订单请求不能为空"; } if (request.getUserId() == null) { return "用户 ID 不能为空"; } if (request.getSkuId() == null) { return "商品 ID 不能为空"; } if (request.getCount() == null || request.getCount() <= 0) { return "购买数量必须大于 0"; } return null; } }

用户状态校验处理器:

// 文件路径:src/main/java/com/example/chaindemo/handler/UserValidHandler.java @Component public class UserValidHandler implements OrderHandler { @Override public String handle(OrderRequest request) { // 模拟查询用户 User user = userMapper.selectById(request.getUserId()); if (user == null) { return "用户不存在"; } if (user.getStatus() != 1) { return "用户已被禁用"; } return null; } }

这里为了演示,用userMapper指代数据访问对象。实际项目中你需要注入自己的UserMapperUserService

库存校验处理器:

// 文件路径:src/main/java/com/example/chaindemo/handler/StockValidHandler.java @Component public class StockValidHandler implements OrderHandler { @Override public String handle(OrderRequest request) { // 模拟查询库存 Stock stock = stockMapper.selectBySkuId(request.getSkuId()); if (stock == null) { return "商品不存在"; } if (stock.getCount() < request.getCount()) { return "库存不足,当前剩余 " + stock.getCount() + " 件"; } return null; } }

风控校验处理器:

// 文件路径:src/main/java/com/example/chaindemo/handler/RiskValidHandler.java @Component public class RiskValidHandler implements OrderHandler { @Override public String handle(OrderRequest request) { // 模拟风控判断 boolean pass = riskService.check(request.getUserId(), request.getSkuId()); if (!pass) { return "订单疑似风险,请稍后重试"; } return null; } }

优惠券校验处理器:

// 文件路径:src/main/java/com/example/chaindemo/handler/CouponValidHandler.java @Component public class CouponValidHandler implements OrderHandler { @Override public String handle(OrderRequest request) { // 优惠券可以为空,为空则不校验 if (request.getCouponId() == null) { return null; } Coupon coupon = couponMapper.selectById(request.getCouponId()); if (coupon == null) { return "优惠券不存在"; } if (coupon.getExpireTime().before(new Date())) { return "优惠券已过期"; } return null; } }

5.6 定义责任链组装器

// 文件路径:src/main/java/com/example/chaindemo/chain/OrderValidateChain.java @Component public class OrderValidateChain { private final List<OrderHandler> handlerList; /** * Spring 会按照 @Order 注解的 value 值从小到大排序, * 把全部 OrderHandler 实现类注入到 handlerList 中。 */ public OrderValidateChain(List<OrderHandler> handlerList) { this.handlerList = handlerList; } /** * 执行订单校验链 */ public String execute(OrderRequest request) { for (OrderHandler handler : handlerList) { String result = handler.handle(request); if (result != null) { return result; } } return null; } }

这里有一个很重要的设计细节:OrderValidateChain构造方法接收List<OrderHandler>。在 Spring Boot 项目中,Spring 会把容器中所有OrderHandler接口的实现类自动收集到这个集合中,省去了手动new的过程。

5.7 控制处理顺序

Spring 注入的List<OrderHandler>顺序默认是不保证的,所以必须用@Order注解显式指定顺序。数值越小优先级越高,最先执行。

// 文件路径:src/main/java/com/example/chaindemo/OrderHandlerConfig.java @Configuration public class OrderHandlerConfig { @Bean @Order(1) public OrderHandler paramValidHandler() { return new ParamValidHandler(); } @Bean @Order(2) public OrderHandler userValidHandler() { return new UserValidHandler(); } @Bean @Order(3) public OrderHandler stockValidHandler() { return new StockValidHandler(); } @Bean @Order(4) public OrderHandler riskValidHandler() { return new RiskValidHandler(); } @Bean @Order(5) public OrderHandler couponValidHandler() { return new CouponValidHandler(); } }

当然,如果处理器类上有@Component注解,也可以直接在类上标注@Order。两种写法效果相同,看团队习惯。这里需要特别注意:如果同时混用@Bean方式和@Component方式注册同名处理器,可能会重复执行同一个校验,一定要避免重复注册。

5.8 业务服务调用链

// 文件路径:src/main/java/com/example/chaindemo/service/OrderService.java @Service public class OrderService { private final OrderValidateChain orderValidateChain; public OrderService(OrderValidateChain orderValidateChain) { this.orderValidateChain = orderValidateChain; } public String createOrder(OrderRequest request) { // 1. 执行校验链 String validateResult = orderValidateChain.execute(request); if (validateResult != null) { return "创建失败:" + validateResult; } // 2. 校验通过,真正创建订单 // orderMapper.insert(...); return "创建成功"; } }

5.9 完整运行效果

写一个简单的单元测试来验证:

// 文件路径:src/test/java/com/example/chaindemo/OrderServiceTest.java @SpringBootTest public class OrderServiceTest { @Autowired private OrderService orderService; @Test public void testCreateOrder() { OrderRequest request = new OrderRequest(); request.setUserId(1001L); request.setSkuId(888L); request.setCount(2); request.setCouponId("COUPON_001"); String result = orderService.createOrder(request); System.out.println("订单结果:" + result); } }

执行结果取决于各处理器的模拟数据。如果所有校验都通过,输出:

订单结果:创建成功

如果风控拦截,输出:

订单结果:创建失败:订单疑似风险,请稍后重试

5.10 业务重构前后对比

重构前,createOrder方法里堆了一大段if-else;重构后,方法变成三步:执行链、判断结果、创建订单。新增一个“地址校验”时,只需要写一个AddressValidHandler implements OrderHandler,在配置类中加一个@Bean并指定顺序即可。已有的五个处理器一行都不用改。

6. 责任链模式进阶:中断与继续两种模式

上面订单校验的例子属于“一票否决”链:只要有一个处理器返回非 null,整条链就中断,不再继续往后执行。这在业务中很常见。

但另一种场景更复杂:每个处理器都要执行,但可以修改请求上下文,直到最后才汇总结果。比如数据清洗流程,第一步转大写、第二步去空格、第三步敏感词替换,这三步是依次执行的,每步都会修改数据,最后输出结果。这种模式可以称为“流水线式责任链”。

要实现这种模式,可以把处理器的返回值从String改为自定义上下文对象:

// 文件路径:com/example/chaindemo/context/PipelineContext.java public class PipelineContext { private String data; public String getData() { return data; } public void setData(String data) { this.data = data; } }
// 文件路径:com/example/chaindemo/handler/PipelineHandler.java public interface PipelineHandler { void handle(PipelineContext context); }
public class PipelineChain { private final List<PipelineHandler> handlerList; public PipelineChain(List<PipelineHandler> handlerList) { this.handlerList = handlerList; } public void execute(PipelineContext context) { for (PipelineHandler handler : handlerList) { handler.handle(context); } } }

这种实现中,请求对象(PipelineContext)在整条链上传递,每个处理器都可以修改它,且链路不中断,直到所有处理器执行完毕。

实际业务中,中断式适合校验、拦截、审批类场景;继续式适合数据加工、内容增强、日志埋点类场景。使用前要明确当前的业务属于哪一种。

7. 常见问题与排查思路

责任链模式虽然不难,但在团队协作和项目落地过程中,还是有不少坑。下面整理高频问题:

问题现象常见原因解决思路
请求没有被任何节点处理,也没有报错链路终端缺少兜底处理器在链尾添加DefaultHandler,返回用户友好提示
处理器执行顺序不对Spring 注入List的顺序不确定@Order注解或@Priority明确顺序
同一处理器被执行了两次@Component@Bean重复注册检查容器中是否注册了两个实例,统一使用一种注册方式
修改链路顺序后影响其他接口全局链被多个入口共用按业务场景拆成多条独立链,避免互相干扰
出现循环调用或死循环链表式实现中next指针形成环组装时避免回头引用,在调试时打印链路节点
链路过长,性能下降处理器数量过多且每个都要查询数据库合并细粒度处理器,或引入前置快速失败机制
在某个处理器中抛出异常后,后续节点继续执行异常被吞掉或没有中断机制明确约定:校验链中抛异常则立即终止,由最外层统一捕获

在实际排查时,可以先按三步走:

  1. 确认组装顺序。打印handlerList,检查每个处理器是否按预期顺序排列。
  2. 确认节点职责。在调试日志中打出每个处理器的类名和入参,定位请求停在哪一步。
  3. 确认中断条件。检查每个处理器的返回逻辑,尤其是return nullreturn "xxx"的分支是否覆盖所有场景。

还有一个容易被忽略的问题:处理器中的数据库查询和外部服务调用通常是有副作用的,如果链路很长而且走不到最后就会被拦截,早期节点查询了多余的数据就是一种性能浪费。在要求极高性能的链路上,可以考虑把“轻量判断”放在前面(比如参数校验、本地缓存校验),把“重量判断”放在后面(比如远程 RPC、数据库查询)。

8. 责任链模式的最佳实践与工程建议

8.1 关于链路组装

不要在每个业务类中手动new责任链。推荐的做法是使用 Spring 容器统一管理,通过构造器注入List<Handler>,再按照@Order确定顺序。这样每个业务类只依赖责任链入口,不关心链上有谁,也不关心顺序如何。

如果项目不是 Spring 环境,也建议把组装代码收敛到一个工厂类或 Builder 类中,避免链的组装逻辑散落各处。

8.2 关于返回值约定

责任链的返回值含义必须统一,否则团队协作时很容易引发歧义。常见约定有:

  • 返回null表示通过,返回字符串表示拦截原因。
  • 或者定义统一的Result对象,包含success字段和message字段。

无论选哪种,都要在接口注释里写清楚,并在代码评审时重点检查。

8.3 关于异常处理

责任链中应尽量避免在每个处理器内单独捕获异常并吞掉。一旦处理器抛出异常,意味着当前处理结果不可信,链路应当中断,由最外层统一处理异常。这样便于通过日志快速定位问题,而不是让请求带着“半处理”的状态继续往后传。

8.4 关于日志链路追踪

建议在每个处理器的出入口打印日志,日志中至少包含请求 ID 和处理器名称。如果链路较长,还可以引入 TraceId,将整条链的调用串起来,排查效率会明显提升。示例格式如下:

[TRACE-1234] ParamValidHandler start, request=OrderRequest(userId=1001) [TRACE-1234] ParamValidHandler end, result=null [TRACE-1234] UserValidHandler start, request=OrderRequest(userId=1001) [TRACE-1234] UserValidHandler end, result=用户已被禁用

8.5 关于职责边界

不要把所有逻辑都塞进责任链。责任链适合“线性、有先后顺序、相对独立”的处理逻辑。如果两个处理器之间存在复杂的依赖关系,或者一个处理器的结果需要回传给前一个处理器,就不太适合用责任链,可以考虑状态机或管道模式。

8.6 关于测试策略

责任链模式的测试建议分成两层:

  • 单测每个 Handler:直接 new 出处理器,传入不同请求,验证处理结果。这一步保证每个节点自身的正确性。
  • 集成测试整条链:验证顺序、中断逻辑以及各处理器之间的数据传递。尤其要测试“所有处理器都通过”和“第一个处理器就拦截”这两个极端场景,以及“链尾处理”的兜底场景。

9. 总结与下一步学习方向

责任链模式的价值在于把“请求处理”的职责从一个大方法中拆解出来,分散到多个独立的处理器中,然后用链的方式把它们组织起来。它解决了if-else嵌套带来的耦合和扩展问题,也让新增处理规则变得简单:加一个类、挂到链上、指定顺序,完事。

本文的核心要点可以总结为四句话:

  • 责任链模式是行为型设计模式,核心是请求发送者和处理者解耦。
  • 链表式更贴近设计模式原意,集合式更适合工程落地。
  • 适合审批流、校验链、过滤链、风控链等线性处理场景。
  • 注意兜底处理器、顺序控制、异常处理、日志追踪四个工程细节。

下一步你可以顺藤摸瓜去学习 Spring MVC 拦截器链和 MyBatis 插件的实现方式,这两个框架级组件都大量运用了责任链思想。读源码时重点看三点:链是如何初始化的、请求是如何在链上传播的、中断逻辑是怎么实现的。看懂之后,再回头看自己项目里的if-else,就会有一种“这也能拆”的感觉了。

如果你正在做的项目里有类似“先 A 再 B 最后 C,任何一个不满足就返回”的逻辑,建议找一小块代码试着用责任链模式重构一下。改完对比一下前后代码量、可读性和扩展自由度,应该能更直观地感受这个模式的好处。如果实践过程中遇到问题,欢迎在评论区交流讨论。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询