责任链模式
深入浅出责任链模式:从机场安检到规则引擎
本文通过实际项目案例,带你彻底理解责任链设计模式的核心思想和应用场景
📖 什么是责任链模式?
想象一下你去机场安检的场景:
旅客 → 检查身份证(第1关) → 检查机票(第2关) → 安检门(第3关) → X光机(第4关) → 人工复检(第5关) → ✅ 通过登机
每一关都有专人负责检查,如果某一关没通过,就直接被拦下,不会继续往后走。只有通过了当前关卡,才会进入下一关。
这就是责任链模式(Chain of Responsibility Pattern)!
官方定义
责任链模式是一种行为设计模式,允许你将请求沿着处理者链传递,直到有一个处理者处理它为止。
简单来说:把复杂的判断逻辑拆成多个小步骤,每个步骤只负责一件事,按顺序执行,任何一步失败就终止。
🎯 为什么需要责任链模式?
场景痛点
假设我们要开发一个保险出险提醒系统,需要对用户的医疗票据进行多维度校验:
- ✅ 年龄是否在 18-60 岁之间
- ✅ 票据金额是否在 500-5000 元范围内
- ✅ 医院是否在白名单中
- ✅ 科室是否符合要求
- ✅ 疾病诊断是否匹配险种
传统写法(if-else 地狱):
点击查看代码
public boolean checkInvoice(Invoice invoice, User user) { // 检查年龄 int age = getAge(user.getIdCard()); if (age < 18 || age > 60) { return false; }
// 检查金额
if (invoice.getAmount() < 500 || invoice.getAmount() > 5000) {
return false;
}
// 检查医院
if (!hospitalWhitelist.contains(invoice.getHospital())) {
return false;
}
// 检查科室
if (!allowedDepartments.contains(invoice.getDepartment())) {
return false;
}
// 检查疾病
if (!diseaseRules.match(invoice.getDisease())) {
return false;
}
return true;
}
问题:
- ❌ 代码冗长,难以阅读
- ❌ 新增规则要修改原有代码,违反开闭原则
- ❌ 规则顺序调整困难
- ❌ 无法复用单个规则
💡 责任链模式的解决方案
核心思想
把每个校验规则封装成独立的"处理器",串联成一条链:
票据 → 年龄规则(Rule1) → 金额规则(Rule2) → 医院规则(Rule3) → 科室规则(Rule4) → 疾病规则(Rule5) → ✅ 通过
类图结构
点击查看代码
┌─────────────────────────┐
│ Rule (接口) │
│ + match(context) │
└──────────┬──────────────┘
│ implements
┌──────────▼──────────────┐
│ AbstractRule (抽象类) │
│ + match(context) │ ◄── 模板方法:串联责任链
│ # doMatch(context) │ ◄── 抽象方法:子类实现
│ # getNextRule() │ ◄── 抽象方法:获取下一个规则
└──────────┬──────────────┘
│ extends
┌─────┴──────┬────────┬──────────┐
│ │ │ │
┌────▼─────┐ ┌───▼────┐ ┌─▼──────┐ ┌─▼────────┐
│AgeRule │ │Amount │ │Hospital│ │Disease │
│ │ │Rule │ │Rule │ │Rule │
└──────────┘ └────────┘ └────────┘ └──────────┘
🔨 代码实现
1. 定义统一接口
所有规则都实现同一个接口,保证能串联起来:
点击查看代码
/**
* 规则接口
*/
public interface Rule {
/**
* 执行规则匹配
* @param context 规则上下文(包含所有需要的数据)
* @return 匹配结果
*/
RuleResult match(RuleContext context) throws Exception;
}
/**
* 规则匹配结果
*/
@Data
@Builder
public class RuleResult {
private boolean isMatch; // 是否匹配成功
private String errorMsg; // 失败原因
private Map<String, Object> data; // 附加数据
}
/**
* 规则上下文(传递数据)
*/
@Data
@Builder
public class RuleContext {
private User user; // 用户信息
private Invoice invoice; // 票据信息
private Policy policy; // 保单信息
private RuleConfig config; // 规则配置
}
2. 抽象基类:串联责任链
这是责任链模式的核心精髓:
点击查看代码
/**
* 抽象规则类:负责串联责任链
*/
public abstract class AbstractRule implements Rule {
/**
* 模板方法:定义责任链的执行流程
*/
@Override
public RuleResult match(RuleContext context) throws Exception {
// 1️⃣ 执行当前规则的匹配逻辑(由子类实现)
RuleResult result = doMatch(context);
// 2️⃣ 如果当前规则通过了
if (result.isMatch()) {
// 3️⃣ 获取下一个规则
Rule nextRule = getNextRule();
// 4️⃣ 如果还有下一个规则,递归调用
if (nextRule != null) {
return nextRule.match(context); // 🔄 递归!
}
}
// 5️⃣ 如果当前规则没通过,或者没有下一个规则了,返回结果
return result;
}
/**
* 具体规则逻辑(由子类实现)
*/
protected abstract RuleResult doMatch(RuleContext context);
/**
* 获取下一个规则实例(由子类实现)
*/
protected abstract Rule getNextRule();
}
关键点:
match()方法是模板方法,定义了责任链的执行流程doMatch()是抽象方法,由子类实现具体的规则逻辑- 如果当前规则通过,就递归调用下一个规则
3. 具体规则实现
每个规则只关心自己的逻辑,互不影响:
年龄规则
点击查看代码
@Component
public class AgeRule extends AbstractRule {
@Override
protected RuleResult doMatch(RuleContext context) {
User user = context.getUser();
RuleConfig config = context.getConfig();
// 获取配置的年龄范围
Integer minAge = config.getMinAge();
Integer maxAge = config.getMaxAge();
// 如果没有配置年龄限制,直接通过
if (minAge == null && maxAge == null) {
return RuleResult.builder().isMatch(true).build();
}
// 从身份证计算年龄
int age = IdCardUtil.getAge(user.getIdCard());
// 判断年龄是否符合规则
boolean match = true;
if (minAge != null && age < minAge) {
match = false;
}
if (maxAge != null && age > maxAge) {
match = false;
}
if (!match) {
return RuleResult.builder()
.isMatch(false)
.errorMsg("年龄不符合规则:要求" + minAge + "-" + maxAge + "岁,实际" + age + "岁")
.build();
}
log.info("年龄规则通过:{}岁", age);
return RuleResult.builder().isMatch(true).build();
}
@Override
protected Rule getNextRule() {
// 告诉系统:我后面是金额规则
return SpringContext.getBean(AmountRule.class);
}
}
金额规则
点击查看代码
@Component
public class AmountRule extends AbstractRule {
@Override
protected RuleResult doMatch(RuleContext context) {
Invoice invoice = context.getInvoice();
RuleConfig config = context.getConfig();
BigDecimal amount = invoice.getAmount();
BigDecimal minAmount = config.getMinAmount();
BigDecimal maxAmount = config.getMaxAmount();
// 如果没有配置金额限制,直接通过
if (minAmount == null && maxAmount == null) {
return RuleResult.builder().isMatch(true).build();
}
// 判断金额是否符合规则
boolean match = true;
if (minAmount != null && amount.compareTo(minAmount) < 0) {
match = false;
}
if (maxAmount != null && amount.compareTo(maxAmount) > 0) {
match = false;
}
if (!match) {
return RuleResult.builder()
.isMatch(false)
.errorMsg("金额不符合规则:要求" + minAmount + "-" + maxAmount + "元,实际" + amount + "元")
.build();
}
log.info("金额规则通过:{}元", amount);
return RuleResult.builder().isMatch(true).build();
}
@Override
protected Rule getNextRule() {
// 告诉系统:我后面是医院规则
return SpringContext.getBean(HospitalRule.class);
}
}
医院规则
点击查看代码
@Component
public class HospitalRule extends AbstractRule {
@Resource
private HospitalService hospitalService;
@Override
protected RuleResult doMatch(RuleContext context) {
Invoice invoice = context.getInvoice();
String hospitalCode = invoice.getHospitalCode();
// 查询医院是否在白名单中
boolean inWhitelist = hospitalService.isInWhitelist(hospitalCode);
if (!inWhitelist) {
return RuleResult.builder()
.isMatch(false)
.errorMsg("医院不在白名单中:" + invoice.getHospitalName())
.build();
}
log.info("医院规则通过:{}", invoice.getHospitalName());
return RuleResult.builder().isMatch(true).build();
}
@Override
protected Rule getNextRule() {
// 告诉系统:我后面是科室规则
return SpringContext.getBean(DepartmentRule.class);
}
}
最后一个规则
点击查看代码
@Component
public class DiseaseRule extends AbstractRule {
@Override
protected RuleResult doMatch(RuleContext context) {
Invoice invoice = context.getInvoice();
Policy policy = context.getPolicy();
// 检查疾病诊断是否匹配险种
boolean match = diseaseService.match(invoice.getDiseaseCode(), policy.getRiskCode());
if (!match) {
return RuleResult.builder()
.isMatch(false)
.errorMsg("疾病诊断不匹配险种")
.build();
}
log.info("疾病规则通过");
return RuleResult.builder().isMatch(true).build();
}
@Override
protected Rule getNextRule() {
// 最后一个规则,返回 null
return null;
}
}
4. 使用方式
点击查看代码
🎬 执行流程演示
场景1:所有规则都通过 ✅
用户:张三,25岁 票据:金额1000元,北京协和医院,内科,感冒
第1关 AgeRule: → doMatch() 检查年龄(25岁在18-60范围内) → 返回 isMatch=true → 调用 getNextRule() 获取 AmountRule → AmountRule.match(context) 递归调用
第2关 AmountRule: → doMatch() 检查金额(1000元在500-5000范围内) → 返回 isMatch=true → 调用 getNextRule() 获取 HospitalRule → HospitalRule.match(context) 递归调用
第3关 HospitalRule: → doMatch() 检查医院(北京协和医院在白名单中) → 返回 isMatch=true → 调用 getNextRule() 获取 DepartmentRule → DepartmentRule.match(context) 递归调用
... 继续往后 ...
最后一关 DiseaseRule: → doMatch() 检查疾病(感冒匹配医疗险) → 返回 isMatch=true → 调用 getNextRule() 返回 null(没有下一关了) → 返回最终结果 isMatch=true
✅ 票据通过所有规则,可以报案!
场景2:某一关没通过 ❌
用户:李四,70岁 票据:金额1000元,北京协和医院,内科,感冒
第1关 AgeRule: → doMatch() 检查年龄(70岁超出18-60范围) → 返回 isMatch=false, errorMsg="年龄不符合规则" → ❌ 不继续往下走了!直接返回失败
❌ 票据被拦截,不会进入后面的金额、医院等检查
💡 责任链模式的优势
1. 解耦
每个规则独立,互不影响。修改年龄规则不需要动金额规则。
2. 符合开闭原则
新增规则只需添加新类,不需要修改现有代码:
点击查看代码
@Component
public class NewRule extends AbstractRule { @Override protected RuleResult doMatch(RuleContext context) { // 你的新逻辑 }
@Override
protected Rule getNextRule() {
return NextRule.class;
}
}
然后在前一个规则的 getNextRule() 中指向它即可。
3. 灵活配置
可以随时调整规则顺序,或者动态跳过某些规则。
4. 职责单一
每个规则只干一件事,代码清晰易懂,方便单元测试。
5. 可复用
单个规则可以在不同的责任链中复用。
🌟 实际应用场景
1. 过滤器链(Filter Chain)
Servlet 中的过滤器就是典型的责任链模式:
点击查看代码
@Component
@Order(1)
public class AuthFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// 认证逻辑
if (isAuthenticated(request)) {
chain.doFilter(request, response); // 传递给下一个过滤器
}
}
}
2. 审批流程
请假审批:组长 → 经理 → 总监 → HR
点击查看代码
public class LeaveApprovalHandler extends AbstractHandler {
@Override
protected ApprovalResult doHandle(ApprovalContext context) {
int days = context.getLeaveDays();
if (days <= 3) {
return approve(); // 组长可以批准3天以内
} else {
return passToNext(); // 超过3天,交给上级
}
}
}
3. 游戏闯关
第1关 → 第2关 → 第3关 → Boss关
4. 日志记录
控制台日志 → 文件日志 → 数据库日志 → 远程日志
5. 异常处理
局部异常处理器 → 全局异常处理器 → 默认异常处理器
⚠️ 注意事项
1. 避免循环引用
确保责任链不会形成环,否则会导致栈溢出:
RuleA → RuleB → RuleC → RuleA ❌ 死循环!
2. 性能考虑
责任链过长会影响性能,建议:
- 将高频失败的规则放在前面(快速失败)
- 控制责任链长度(一般不超过10个节点)
3. 调试困难
责任链的调用链路较长,建议:
- 在每个规则中添加日志
- 记录规则执行耗时
- 使用链路追踪工具
4. Spring Bean 管理
在 Spring 项目中,建议使用 @Component + 依赖注入管理规则实例:
点击查看代码
@Component
public class AgeRule extends AbstractRule { @Resource private AmountRule amountRule; // 注入下一个规则
@Override
protected Rule getNextRule() {
return amountRule;
}
}
🎯 总结
核心要点
| 要素 | 说明 |
|---|---|
| 统一接口 | 所有规则实现同一个接口 |
| 抽象基类 | 定义责任链的串联逻辑(模板方法) |
| 具体规则 | 实现自己的业务逻辑 |
| 递归调用 | 通过后调用下一个规则 |
| 快速失败 | 任意一步失败立即返回 |
何时使用责任链模式?
✅ 有多个对象可以处理同一个请求
✅ 希望在不明确指定接收者的情况下,向多个对象中的一个提交请求
✅ 可动态指定一组对象处理请求
✅ 想避免请求发送者与接收者耦合
一句话总结
责任链模式 = 多个处理器串联 + 递归调用 + 快速失败
就像机场安检一样,一关一关过,过了这关才能进下一关,任何一关没过就直接被拦下!

浙公网安备 33010602011771号