责任链模式

深入浅出责任链模式:从机场安检到规则引擎

本文通过实际项目案例,带你彻底理解责任链设计模式的核心思想和应用场景

📖 什么是责任链模式?

想象一下你去机场安检的场景:

旅客 → 检查身份证(第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;
    }
}

🎯 总结

核心要点

要素 说明
统一接口 所有规则实现同一个接口
抽象基类 定义责任链的串联逻辑(模板方法)
具体规则 实现自己的业务逻辑
递归调用 通过后调用下一个规则
快速失败 任意一步失败立即返回

何时使用责任链模式?

✅ 有多个对象可以处理同一个请求
✅ 希望在不明确指定接收者的情况下,向多个对象中的一个提交请求
✅ 可动态指定一组对象处理请求
✅ 想避免请求发送者与接收者耦合

一句话总结

责任链模式 = 多个处理器串联 + 递归调用 + 快速失败

就像机场安检一样,一关一关过,过了这关才能进下一关,任何一关没过就直接被拦下!


posted @ 2026-04-14 11:14  噗噗噗i丶  阅读(17)  评论(0)    收藏  举报