编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • 面向对象设计

  • 常见设计原则

  • 巧学设计模式

    • README
    • 单例模式设计思想
    • 工厂模式设计思想
    • 建造者模式设计思想
    • 原型模式设计思想
    • 静态代理设计模式
    • 动态代理设计模式
    • 适配器模式设计思想
    • 装饰者模式设计思想
    • 外观模式设计思想
    • 桥接模式设计思想
    • 组合模式设计思想
    • 享元模式设计思想
    • 观察者模式设计思想
    • 策略者模式设计思想
    • 模版模式设计思想
    • 迭代器模式设计思想
    • 职责链模式设计思想
      • 01.案例引入与思考
        • 1.1 痛点现场
        • 1.2 直觉实现复现
        • 1.3 问题根源拆解
        • 1.4 引出本篇主角
      • 02.3次失败探索
        • 2.1 尝试方案A:if-else 硬编码全部校验
        • 2.2 尝试方案B:List<Handler\> + for 循环遍历
        • 2.3 尝试方案C:策略模式,调用方手动串联
        • 2.4 终于引出职责链模式
      • 03.职责链模式基础
        • 3.1 从失败中提炼需求
        • 3.2 职责链模式的标准骨架
        • 3.3 典型使用场景
      • 04.4种实现对比
        • 4.1 实现核心要点
        • 4.2 实现A:纯职责链 GoF 经典版
        • 4.3 实现B:过滤链 Servlet Filter 风格
        • 4.4 实现C:环绕链/洋葱模型 OkHttp Interceptor
        • 4.5 实现D:Spring Security FilterChain
        • 4.6 4种实现速查表
      • 05.用前用后效果对比
        • 5.1 代码量 & 可维护性对比
        • 5.2 规则插队 & 顺序控制对比
        • 5.3 核心收益
      • 06.反面踩坑实录
        • 6.1 踩坑A:链节点忘记调 next → 请求丢失
        • 6.2 踩坑B:链顺序错位 → 反向拦截
        • 6.3 踩坑C:链节点抛异常 → 整链中断
        • 6.4 踩坑D:链长度无上限 → 延迟雪崩
        • 6.5 踩坑E:环绕链 before/after 不成对
        • 6.6 踩坑F:链节点共享可变状态
        • 6.7 开源案例速查 & 替代方案汇总
      • 07.决策树与选型
        • 7.1 该不该用职责链模式
        • 7.2 选哪种实现方式
        • 7.3 选型清单速查
      • 08.总结与延伸
        • 8.1 设计思想沉淀
        • 8.2 模式联动边界
        • 8.3 思考题与延伸
    • 命令模式设计思想
    • 状态模式设计思想
    • 备忘录模式设计思想
    • 中介者模式设计思想
    • 访问者模式设计思想
    • 解释器模式设计思想
    • 23种设计模式概括
    • 技术写作模板
  • 系统架构设计

  • 编程
  • 巧学设计模式
杨充
2022-12-01
目录

职责链模式设计思想

# 17.职责链模式设计思想

📚 本篇渐进学习节奏(建议按顺序食用)

  1. 第 01 节 · 案例引入 — 双 11 大促直播带货,内容审核 287 行 if-else 5 团队抢改,新增"反诈骗"规则插队漏审 → 监管通报封禁 14 天 → GMV 损失 2300 万的 P0 事故
  2. 第 02 节 · 3 次失败探索 — if-else 硬编码 / List 遍历 / 策略模式手动串联,三种方案为何全部翻车
  3. 第 03 节 · 模式基础 — 从失败清单逆推设计约束 → Handler 链 + toNext 传递 + 两种变体
  4. 第 04 节 · 4 种实现对比 — 纯职责链 → 过滤链 → 环绕链/洋葱模型 → Spring Security FilterChain
  5. 第 05 节 · 效果对比 — 用前用后 13 维数据说话(事故现场 vs 职责链重构)
  6. 第 06 节 · 反面踩坑 — 6 种翻车姿势实录 + 15 个开源案例 + 替代方案汇总
  7. 第 07 节 · 决策树 — 该不该用 → 用哪种实现 → 速查清单,贴工位上就能用
  8. 第 08 节 · 总结延伸 — 思考模型沉淀 + 与策略/装饰器/命令/管道的精准切割 + 3 道自测题

阅读到任一节卡壳,直接跳回上一节复盘场景;本篇所有代码均可直接运行。

# 目录介绍

  • 01.案例引入与思考
    • 1.1 痛点现场
    • 1.2 直觉实现复现
    • 1.3 问题根源拆解
    • 1.4 引出本篇主角
  • 02.3次失败探索
    • 2.1 尝试方案A:if-else 硬编码全部校验
    • 2.2 尝试方案B:List<Handler> + for 循环遍历
    • 2.3 尝试方案C:策略模式,调用方手动串联
    • 2.4 终于引出职责链模式
  • 03.职责链模式基础
    • 3.1 从失败中提炼需求
    • 3.2 职责链模式的标准骨架
    • 3.3 典型使用场景
  • 04.4种实现对比
    • 4.1 实现核心要点
    • 4.2 实现A:纯职责链 GoF 经典版
    • 4.3 实现B:过滤链 Servlet Filter 风格
    • 4.4 实现C:环绕链/洋葱模型 OkHttp Interceptor
    • 4.5 实现D:Spring Security FilterChain
    • 4.6 4种实现速查表
  • 05.用前用后效果对比
    • 5.1 代码量 & 可维护性对比
    • 5.2 规则插队 & 顺序控制对比
    • 5.3 核心收益
  • 06.反面踩坑实录
    • 6.1 踩坑A:链节点忘记调 next → 请求丢失
    • 6.2 踩坑B:链顺序错位 → 反向拦截
    • 6.3 踩坑C:链节点抛异常 → 整链中断
    • 6.4 踩坑D:链长度无上限 → 延迟雪崩
    • 6.5 踩坑E:环绕链 before/after 不成对
    • 6.6 踩坑F:链节点共享可变状态
    • 6.7 开源案例速查 & 替代方案汇总
  • 07.决策树与选型
    • 7.1 该不该用职责链模式
    • 7.2 选哪种实现方式
    • 7.3 选型清单速查
  • 08.总结与延伸
    • 8.1 设计思想沉淀
    • 8.2 模式联动边界
    • 8.3 思考题与延伸

# 01.案例引入与思考

本篇主线:多个处理器依次校验一个请求,主流程被 if-else 淹没——引入职责链后,每节独立封装的 Handler 链式传递,主流程只接触链头。

# 1.1 痛点现场

🔥 模拟事故复盘 · 双 11 大促 · "直播带货违禁词漏审,平台被通报封禁 14 天,GMV 损失 2300 万"

双 11 大促预热第三天,头部主播 A(粉丝 4800 万,坑位费 80 万/场)开播 4 小时大场。直播间在线峰值 230 万人,GMV 跑到 6800 万正在冲榜。18:23,主播在介绍一款"金融理财产品"时,使用了违规承诺收益话术 + 涉嫌虚假宣传——监管实时巡查捕捉到,但内容审核服务全程返回"通过",后台日志显示违规内容根本没进过审核。

4 小时直播结束,当晚监管下达"违反广告法 + 金融营销违规"双重通报。次日凌晨 03:30,平台收到行政处罚:

  • 官方主账号封禁 14 天(直播 + 短视频 + 站内广告全停);
  • 大促剩余 18 天直播日历降权(流量减少 60%);
  • 罚款 + 整改报告 + 高管面谈。

损失复盘:

  • 直接 GMV 损失:14 天封禁 + 18 天降权,日均直播 GMV 1.6 亿 → 2300 万(平台抽佣折损);
  • 主播赔款:头部 A 后续 6 场无法履约 → 赔款 480 万 + 经纪公司解约威胁;
  • 品牌赔偿:本场 18 家品牌因违规连带处理 → 赔偿 + 公关 670 万;
  • 监管成本:法务 3 周整改 + 5 高管面谈 + 政策合规岗紧急扩招 12 人。

复盘会上,内容审核服务 LiveContentReviewService 翻出来——整整 287 行 if-else,5 个团队轮流加规则,没人敢动:

public class LiveContentReviewService {
    public ReviewResult review(LiveContent content) {
        if (containsSensitiveWord(content)) return reject("敏感词");      // 2019 内容团队
        if (containsAdLawViolation(content)) return reject("广告法");     // 2020 法务团队
        if (containsPornPolitics(content)) return reject("涉黄涉政");     // 2021 安全团队
        if (content.getCategory() == Category.FINANCE
            && containsGamblingFraud(content)) return reject("涉赌涉诈"); // 2022 金融合规
        if (content.getType() == ContentType.PRODUCT_INTRO
            && containsFalseAdvertising(content)) return reject("虚假宣传"); // 2023 质量
        // ❌ 永远走不到!金融营销违规在前面被广告法 catch-all 吃掉了
        if (content.getCategory() == Category.FINANCE
            && containsFinanceMarketingViolation(content)) return reject("金融违规");
        // ……还有 280 行
        return pass();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

根因有 4 个,一个比一个炸裂:

  1. 287 行 if-else 没人敢动 → 规则顺序错位:金融营销违规被前序分支的 catch-all 静默吃掉,永远走不到;
  2. 审核类型分支硬编码 → 直播口播未接入虚假宣传:虚假宣传只对 PRODUCT_INTRO 生效,LIVE_VOICE 根本不走;
  3. 新增反诈骗规则插队到第一行 → 破坏所有分支结构:新人把规则插到 if 链顶部,后面 6 个规则的 else-if 链被改散;
  4. 5 团队各自加规则 → 顺序/优先级/灰度全靠口头约定:没有统一注册中心、没有版本号、没有审计日志。

复盘结论不是"代码不严谨"——而是 "多步校验"和"主流程"在 287 行 if-else 里被强行耦合,5 个团队都往同一个方法里塞规则,任何一个新规则插入都可能破坏前面所有规则的结构。

# 1.2 直觉实现复现

你也能写出这种代码。 电商下单接口在落库前要依次做 6 道校验,新人的第一反应就是全部塞在一个方法里:

public OrderResult createOrder(OrderReq req) {
    if (!loginCheck(req)) return fail("未登录");
    if (!riskCheck(req))  return fail("风控拦截");
    if (!stockCheck(req)) return fail("库存不足");
    if (!couponCheck(req)) return fail("优惠券不可用");
    if (!priceCheck(req)) return fail("价格异常");
    if (!rateLimit(req))  return fail("请求太频繁");
    return doCreate(req);
}
1
2
3
4
5
6
7
8
9

🧪 跑一下,亲眼看到 bug

// 1. 产品:加一道"黑名单校验" → 在 createOrder 中间插一个 if
//    → 和其他 6 个 if 混在一起 → 很难肉眼判断插在哪一步前后合适
// 2. 运营:VIP 免风控 + 免限流 → 在 if 前加 if(vip) skip → if-else 嵌套爆炸
// 3. 支付、退款、取消订单也要做类似校验 → 各自复制一份 if-else
//    → 风控规则改了 → 3 个地方都要改 → 总会漏 1 个
// 4. 大促想灰度关闭"防刷限流" → 在 if 前加 featureFlag → 越塞越乱
1
2
3
4
5
6

事故现场重现完毕——"多个校验步骤"和"主流程"焊死在同一个方法里,每加/删/调序一个校验都要改核心方法,改动风险随步骤数指数增长。

💭 3 个反思题(先别往下看,自己想 30 秒):

  1. 如果校验从 6 步涨到 20 步,createOrder 方法有多长?
  2. 能否在不改 createOrder 的情况下,新增一步"黑名单校验"?
  3. 能否在配置中心一键调整"风控"和"限流"的执行顺序?

# 1.3 问题根源拆解

画一张图就清楚了:

flowchart LR
    Req[OrderReq] --> F[createOrder 方法体]
    F --> L[loginCheck]
    F --> R[riskCheck]
    F --> S[stockCheck]
    F --> C[couponCheck]
    F --> P[priceCheck]
    F --> RL[rateLimit]
    F --> DB[doCreate 入库]
    style F fill:#fee
1
2
3
4
5
6
7
8
9
10

所有校验都硬塞在 createOrder 里,互不隔离,这就埋下了 N 类隐患:

隐患 现象 业务影响
主流程被校验淹没 createOrder 本该关心"下单落库",被 6 个 if-else 围起来 Code Review 找不到核心逻辑
顺序写死 风控要换到库存之后 → 必须改主流程 改一行牵全身
校验不可插拔 灰度关闭"防刷"、VIP 跳过"风控" → if-else 里塞开关 if-else 嵌套爆炸
复用困难 支付/退款/取消订单也要做部分相同校验 → 各自复制 改规则漏同步 → 不一致
扩展要改核心 新增"黑名单校验" → 必须改 createOrder 违反开闭原则

核心矛盾:业务上"多步校验是一个链式管道",但代码层面所有步骤挤在同一个方法里——步骤之间没有独立的封装边界,新增/删除/调序都是改同一个地方。

# 1.4 引出本篇主角

职责链模式(Chain of Responsibility)的核心思想:把每个处理器做成独立的类,各自持有一个"下一环"引用,形成一条链。请求沿链传递,每个处理器只关心自己要不要处理、要不要继续传;调用方只和链头打交道。

abstract class OrderHandler {
    protected OrderHandler next;
    public OrderHandler linkWith(OrderHandler n) { this.next = n; return n; }
    public abstract OrderResult handle(OrderReq req);
    protected OrderResult toNext(OrderReq req) {
        return next == null ? OrderResult.ok() : next.handle(req);
    }
}

class LoginHandler extends OrderHandler {
    public OrderResult handle(OrderReq r) {
        if (!logined(r)) return fail("未登录");
        return toNext(r);
    }
}
// RiskHandler / StockHandler / CouponHandler / PriceHandler / RateLimitHandler 类似

// 组装链
OrderHandler head = new LoginHandler();
head.linkWith(new RiskHandler())
    .linkWith(new StockHandler())
    .linkWith(new CouponHandler())
    .linkWith(new PriceHandler())
    .linkWith(new RateLimitHandler());

// 调用方只认链头
public OrderResult createOrder(OrderReq req) {
    OrderResult r = head.handle(req);
    return r.ok() ? doCreate(req) : r;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
flowchart LR
    Req[请求] --> H1[LoginHandler]
    H1 -->|通过| H2[RiskHandler]
    H2 -->|通过| H3[StockHandler]
    H3 -->|通过| H4[CouponHandler]
    H4 -->|通过| H5[PriceHandler]
    H5 -->|通过| H6[RateLimitHandler]
    H6 -->|通过| End[doCreate]
    H1 -.失败.-> Fail[直接返回失败]
    H3 -.失败.-> Fail
    style H1 fill:#e6f3ff
    style H2 fill:#e6f3ff
    style H3 fill:#e6f3ff
    style H4 fill:#e6f3ff
    style H5 fill:#e6f3ff
    style H6 fill:#e6f3ff
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

链的两种经典形态(本篇会详谈):

flowchart LR
    subgraph 纯链["纯职责链: 一人处理全链结束(审批流)"]
        A1[Handler1] -->|能处理| A2[处理并返回]
        A1 -->|不能| A3[Handler2]
    end
    subgraph 过滤链["过滤链: 人人都处理(Servlet Filter)"]
        B1[Filter1] --> B2[Filter2] --> B3[Filter3] --> End[真正目标]
    end
    style A2 fill:#dfd
    style End fill:#dfd
1
2
3
4
5
6
7
8
9
10

Servlet FilterChain、Spring Security、OkHttp Interceptor、Netty ChannelPipeline——它们本质都是职责链。

但是!先别急着看实现。下一节,我们先看看新人通常会先尝试哪些"看起来很合理"的方案,并理解它们为什么都不够好。

# 02.3次失败探索

为什么要学这一节:直接给你"标准答案"是很容易的,但你要知道,职责链不是凭空发明的——它是前人走过 3 条死路之后才提炼出来的。走过这些死路,你才会真正理解为什么代码长那个样子。

# 2.1 尝试方案A:if-else 硬编码全部校验

【新人方案①:把所有校验 if-else 塞在一个方法里——一个方法搞定全部】

"下单前就 6 道校验,用 if-else 一路拦下来不就行了?简单直接!"

// 代码同 1.2,这里不再重复
public OrderResult createOrder(OrderReq req) {
    if (!loginCheck(req)) return fail(...);
    if (!riskCheck(req))  return fail(...);
    // ...
}
1
2
3
4
5
6

🧪 跑一下,看会出什么问题

// 场景:5 个团队往上加校验 → 287 行 if-else(1.1 事故现场)
// 1. 新人加"反诈骗"规则 → 插到第 1 行 → 后面的 else-if 链全部被破坏
// 2. 金融营销违规被前序的 catch-all 静默吃掉 → 永远走不到
// 3. 谁先执行谁后执行全靠代码顺序,没有任何显式标记

// 本质:每加/删/调序一个校验,都要改同一个方法
//       5 个团队改同一个文件 → git 冲突频发 → merge 时规则互相覆盖
1
2
3
4
5
6
7

❌ 失败原因:所有校验耦合在一个方法里,新增/删除/调序全部改核心,多团队协作时规则互相破坏。本题事故 287 行 if-else、GMV 2300 万就是铁证。

💡 反思:我们需要"每个校验独立封装 + 新增校验不改主流程 + 显式控制顺序"。

# 2.2 尝试方案B:List<Handler> + for 循环遍历

【新人方案②:把 Handler 放进 List,主流程里 for 循环遍历】

"那我用 List 存所有 Handler,for 循环遍历——新增 Handler 加到 List 里就行,不用改主流程!"

public class OrderService {
    private List<OrderHandler> handlers = new ArrayList<>();

    public void addHandler(OrderHandler h) { handlers.add(h); }

    public OrderResult createOrder(OrderReq req) {
        for (OrderHandler h : handlers) {
            OrderResult r = h.handle(req);   // ❌ 每个 Handler 必须执行,无法短路
            if (!r.ok()) return r;
        }
        return doCreate(req);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

🧪 跑一下,会发现隐藏问题

// 问题 1:每个 Handler 必须执行,无法短路跳过
//    → 审批流需求:经理批 5 万以内的单,不用传到总监
//    → 但 for 循环无论经理批没批,都会继续调用总监 → 不符合"命中即止"语义

// 问题 2:顺序控制靠 List.add 的顺序 → 可视化差
//    → 谁加了哪些 Handler、顺序是什么,不读代码不知道

// 问题 3:无法实现"环绕"语义
//    → OkHttp Interceptor 需要在 proceed() 前后各做事情
//    → 纯 for 循环做不到 before/after 配对
1
2
3
4
5
6
7
8
9
10

❌ 失败原因:

  1. 无短路语义:for 循环把所有 Handler 都调一遍,"命中即止"做不到;
  2. 顺序不可视:List 里有什么、顺序是什么,必须翻代码——没有链式可视结构;
  3. 无法环绕:before/after 配对的环绕链(如 OkHttp Interceptor)for 循环做不了。

💡 反思:我们要的不仅是"多个处理器串行执行",还要每个处理器自己决定"是否继续传递"——这就是链的 next 引用 + toNext() 方法的意义。

# 2.3 尝试方案C:策略模式,调用方手动串联

【新人方案③:用策略模式——每个校验是一个 PricingStrategy 风格的接口,调用方逐个调用】

"每个校验都是一个独立的策略对象,调用方按顺序逐个调——看起来很像职责链?"

interface CheckStrategy {
    Result check(OrderReq req);
}

class LoginCheck implements CheckStrategy { ... }
class RiskCheck implements CheckStrategy { ... }

public OrderResult createOrder(OrderReq req) {
    // ❌ 调用方需要知道所有策略,并手动串联
    List<CheckStrategy> checks = Arrays.asList(
        new LoginCheck(), new RiskCheck(), new StockCheck(), ...
    );
    for (CheckStrategy c : checks) {
        Result r = c.check(req);     // ❌ 同一个问题:无法短路
        if (!r.ok()) return r;       // 调用方自己控制短路
    }
    return doCreate(req);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

🧪 跑一下,看会怎样

// 方案B 的 for 循环问题这里全有:无法短路、无法环绕
// 而且还有新问题:
// 1. 调用方必须知道"有哪些策略" → 又回到了集中式管理
// 2. 策略模式的本意是"选一个执行",职责链是"多个接力"
//    → 选一个 vs 接力 → 语义完全不同的两个模式,混用必乱
// 3. 链的"toNext()"语义丢失 → 调用方成了新的上帝类
1
2
3
4
5
6

❌ 失败原因:

  1. 策略模式 = "选一个",职责链 = "多个接力"——语义不同,混用必乱;
  2. 调用方必须知道全部策略并手动串联 → 集中式管理又回来了;
  3. 失去了链的核心语义——节点自己决定是否传递。

💡 反思:必须让每个处理器持有 next 引用,自己调用 next.handle()——不是调用方里 for 循环,而是节点内递归传递。

# 2.4 终于引出职责链模式

【3 次失败之后,需求清单收敛了】

必须满足 来自哪一次失败
① 每节独立封装,新增不改核心 2.1 方案A
② 节点自己控制短路/继续传(next 引用) 2.2 方案B
③ 链式结构可视,顺序一目了然 2.2 方案B
④ 不是"调用方选一个",是"节点接力传递" 2.3 方案C

【职责链模式的标准答案】

// ① 抽象处理器 + next 引用
abstract class Handler {                             
    protected Handler next;                         // ② 链式引用
    public Handler setNext(Handler n) { this.next = n; return n; }

    public final Result handle(Request req) {        // ③ 链式传递模板
        Result r = doHandle(req);
        if (r != null) return r;                     // ② 短路控制!
        if (next != null) return next.handle(req);   // ② 继续传递
        return Result.ok();
    }
    protected abstract Result doHandle(Request req);  // ① 子类只写处理逻辑
}
1
2
3
4
5
6
7
8
9
10
11
12
13

短短几行,同时回答了上面 4 个需求。这就是职责链模式的"灵魂代码"——链表 + 多态 + 短路控制。

# 03.职责链模式基础

# 3.1 从失败中提炼需求

回顾 02 节,我们试了 if-else 硬编码、List 遍历、策略模式串联——全部失败。现在拿着这些失败报告,问自己一个问题:

"如果我要写一套跑 3 年不崩的多步校验/审核系统,它必须满足哪几条硬约束?"

把这些约束写下来,就自然得到了职责链的设计清单:

约束 来自 代码体现
① 每节独立封装,新增不改核心 2.1 方案A 子类继承 Handler,只写 doHandle(),主流程不变
② 节点自己控制短路/继续传 2.2 方案B next 引用 + return next.handle(req) 递归传递
③ 链式结构可视 2.2 方案B new A().setNext(new B()).setNext(new C()) 一目了然
④ 不是"选一个"而是"接力" 2.3 方案C doHandle() 返回 null 表示"我不管,继续传"

职责链模式(Chain of Responsibility):让多个对象都有机会处理请求,从而避免请求发送者与接收者的耦合。把这些对象连成一条链,请求沿链传递,直到有对象处理它。

两种变体(本篇都会详谈):

  • 纯职责链(GoF 经典):一旦命中就终止传递(如审批流:经理批完不传总监)
  • 过滤链(变体):全员都过一遍(如 Servlet Filter:每个 Filter 都执行)

# 3.2 职责链模式的标准骨架

上面 4 条约束翻译成代码,所有实现变体共用一个骨架:

// 抽象处理器
public abstract class Handler {                     
    protected Handler next;                         // ② 链式引用

    public Handler setNext(Handler n) {             // ③ 链式装配
        this.next = n; return n;
    }

    // ① 模板方法:子类只写 doHandle
    public final Result handle(Request req) {
        Result r = doHandle(req);                    // ① 子类只关心处理逻辑
        if (r != null) return r;                     // ② 有结果 → 短路返回
        if (next != null) return next.handle(req);   // ② 无结果 → 继续传
        return Result.ok();                          // 走到尾部 → 全部通过
    }

    protected abstract Result doHandle(Request req); // ① 子类只需实现这个
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

三句话记住:① Handler 持有 next 引用 → ② 子类只写 doHandle(),返回 null 表示继续传 → ③ 主流程只接触链头。差异全在 短路 vs 全过 / 单向 vs 环绕 / 手动装配 vs 容器管理 里——这就是下一节 4 种实现的核心分岔。

职责链模式包含如下角色:

  • Handler(抽象处理器):定义 handle() + next 引用
  • ConcreteHandler(具体处理器):实现 doHandle(),决定处理 or 传递
  • Client(客户端):组装链,把请求丢给链头

# 3.3 典型使用场景

不是所有"多步骤"场景都适合职责链。核心判断标准:"多步串行处理,每步独立可插拔,且需要可以短路/全过两种语义"。以下场景验证:

  • 下单校验链(本篇主线):登录→风控→库存→优惠券→价格→防刷——每步独立 Handler,新品/删/调序不改主流程;
  • 内容审核链(1.1 事故):敏感词→广告法→涉黄涉政→金融合规→反诈骗——5 团队各自维护各自 Handler;
  • Servlet Filter 链(4.3):FilterChain.doFilter() 串起认证/日志/压缩/编码等 N 个 Filter;
  • OkHttp Interceptor 链(4.4):重试/缓存/日志/编解码器串成洋葱模型;
  • 审批流:经理→总监→VP→CEO——金额 ≤ 5 万经理处理即止,不往上传递。

反面提醒:步骤 ≤ 3 且不增长、每步必须全部执行且顺序固定——参考 07 节决策树。

# 04.4种实现对比

# 4.1 实现核心要点

4 种写法本质上是在 短路 vs 全过 / 单向 vs 环绕 / 手动 vs 框架 上的不同取舍。实现职责链只需三行骨架代码:

// ① 抽象处理器 + next 引用
protected Handler next;

// ② 子类实现处理逻辑
protected abstract Result doHandle(Request req);

// ③ 链式传递
if (next != null) return next.handle(req);
1
2
3
4
5
6
7
8

差异全在"要不要短路 / 要不要环绕 / 用不用框架"里。下面按演进顺序逐一展开。

# 4.2 实现A:纯职责链 GoF 经典版

设计权衡:用"每节必须显式判断是否传递"换"精准短路控制"。

选它的理由:审批流 / 风控链等"命中即止"的语义。

以下单 6 道校验链为案例:

public abstract class OrderHandler {
    protected OrderHandler next;
    public OrderHandler then(OrderHandler n) { this.next = n; return n; }

    // ① 模板方法:短路 + 传递
    public final Result handle(Order o) {
        Result r = doHandle(o);
        if (!r.success() || next == null) return r;    // ② 短路!
        return next.handle(o);                           // ② 继续传
    }
    protected abstract Result doHandle(Order o);         // ① 子类只需实现这个
}

public class LoginHandler extends OrderHandler {
    protected Result doHandle(Order o) {
        return o.user != null ? Result.ok() : Result.fail("未登录");
    }
}
public class RiskHandler extends OrderHandler { /* doHandle() ... */ }
public class StockHandler extends OrderHandler { /* doHandle() ... */ }

// ③ 组装链
OrderHandler chain = new LoginHandler();
chain.then(new RiskHandler()).then(new StockHandler()).then(new CouponHandler())
     .then(new PriceHandler()).then(new RateLimitHandler());

// 调用
Result r = chain.handle(order);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

新增"黑名单校验":

// ③ 链式结构——改动只在装配处
chain.then(new BlackListHandler());   // 一行搞定
1
2

技术分析:

  • 短路控制精准:doHandle() 返回失败即止,不继续传递——审批流的本质;
  • 链式装配可视:new A().then(new B()).then(new C()) 链路一目了然;
  • 新增只改装配:新增 Handler 只需写类 + 装配链,主流程零改动;
  • 纯单向:不支持环绕语义(before/after),如需环绕见 4.4。

# 4.3 实现B:过滤链 Servlet Filter 风格

设计权衡:用"每人都过一遍 + Chain 传递控制"换"全量执行的可靠性"。

选它的理由:Web 框架的 Filter/Interceptor 链(认证→日志→压缩→编码,人人都执行)。

// ① Filter 接口
public interface Filter {
    void doFilter(Request req, Response resp, FilterChain chain) throws Exception;
}

// ② FilterChain:核心控制链
public interface FilterChain {
    void doFilter(Request req, Response resp) throws Exception;
}

// ③ FilterChain 实现
public class DefaultFilterChain implements FilterChain {
    private final List<Filter> filters;
    private int position = 0;                        // 当前位置

    public DefaultFilterChain(List<Filter> filters) { this.filters = filters; }

    @Override
    public void doFilter(Request req, Response resp) throws Exception {
        if (position < filters.size()) {
            Filter filter = filters.get(position++);  // 取下一个
            filter.doFilter(req, resp, this);          // ② Filter 内部决定是否继续
        }
        // position >= size → 全部执行完毕 → 到达真正目标
    }
}

// ④ 具体 Filter:认证过滤器
public class AuthFilter implements Filter {
    public void doFilter(Request req, Response resp, FilterChain chain) throws Exception {
        if (!req.isLoggedIn()) {
            resp.setStatus(401);
            return;                                    // ② 短路:不调 chain.doFilter
        }
        chain.doFilter(req, resp);                     // ② 继续传递
    }
}

// LogFilter / CompressFilter 类似

// ⑤ 组装 + 调用
Servlet.service(req, resp) {
    FilterChain chain = new DefaultFilterChain(
        Arrays.asList(new AuthFilter(), new LogFilter(), new CompressFilter())
    );
    chain.doFilter(req, resp);
    // 走到这里 → 所有 Filter 执行完毕 → 处理业务逻辑
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

技术分析:

  • 全量执行 + 可控短路:每个 Filter 内部自己决定是否 chain.doFilter();不调用 = 短路;
  • FilterChain 集中管理:用 position 计数器代替链表 next 引用——更适合框架层统一调度;
  • 支持多个 Filter 轻易注册:List<Filter> 比链表更灵活——增删改只需改配置;
  • Spring Security SecurityFilterChain:16+ 个 Filter(UsernamePasswordAuth / Session / Csrf / Logout / ...)串成一条链。

# 4.4 实现C:环绕链/洋葱模型 OkHttp Interceptor

设计权衡:用"proceed() 前后各执行一段逻辑"换"before/after 成对的环绕语义"。

选它的理由:需要"请求前做什么 + 请求后做什么"成对出现的场景(日志/计时/重试/缓存)。

// ① Interceptor 接口
public interface Interceptor {
    Response intercept(Chain chain) throws IOException;

    // ② Chain 内部接口
    interface Chain {
        Request request();
        Response proceed(Request request) throws IOException;  // ③ 继续传递
    }
}

// ③ 日志拦截器:环绕执行
public class LoggingInterceptor implements Interceptor {
    public Response intercept(Chain chain) throws IOException {
        Request req = chain.request();
        long start = System.nanoTime();
        log.info(">>> " + req.url());           // ← before

        Response resp = chain.proceed(req);      // ← 调用下一节

        long ms = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
        log.info("<<< " + resp.code() + " (" + ms + "ms)");  // ← after
        return resp;
    }
}

// ③ 重试拦截器
public class RetryInterceptor implements Interceptor {
    private int maxRetries = 3;

    public Response intercept(Chain chain) throws IOException {
        IOException lastException = null;
        for (int i = 0; i < maxRetries; i++) {
            try {
                return chain.proceed(chain.request());  // ← 失败就重试
            } catch (IOException e) {
                lastException = e;
            }
        }
        throw lastException;
    }
}

// ④ RealInterceptorChain:控制流转
public class RealInterceptorChain implements Interceptor.Chain {
    private final List<Interceptor> interceptors;
    private int index = 0;

    public Response proceed(Request request) throws IOException {
        if (index >= interceptors.size()) throw new AssertionError();
        Interceptor interceptor = interceptors.get(index++);
        return interceptor.intercept(this);     // ③ 递归: 每个 Interceptor 处理前后都可做事
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

技术分析:

  • 洋葱模型:proceed() 的前后就是 before/after——日志、计时、重试、缓存等成对操作的核心;
  • 递归调用链:index++ 递进调用下一个 Interceptor,栈自动维护回退顺序;
  • OkHttp 默认 5 层:应用层 → 重试 → Bridge → Cache → Connect → 网络层;
  • 必须成对:before 和 after 不能拆分——否则资源泄漏(见 6.5 踩坑E)。

# 4.5 实现D:Spring Security FilterChain

设计权衡:用"Spring 容器管理 + @Order 排序 + 多条链按路径匹配"换"企业级安全框架"。

选它的理由:企业级 Web 安全需要 16+ 个 Filter 按路径/角色动态切换不同链路。

// ① Spring Security Filter 注册
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .csrf(CsrfConfigurer::disable)                  // ③ CSRF Filter
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/public/**").permitAll()  // ③ 路径匹配
            .requestMatchers("/api/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated()
        )
        .formLogin(Customizer.withDefaults())           // ③ UsernamePasswordAuth Filter
        .sessionManagement(session -> session            // ③ Session Filter
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .addFilterBefore(jwtAuthFilter(),                // ③ 自定义 Filter 插入
            UsernamePasswordAuthenticationFilter.class);
    return http.build();
}

// ② FilterChainProxy 内部维护多条 SecurityFilterChain
//    按 RequestMatcher 匹配 → 走对应的 Filter 链
//    例如:/api/admin/** → AdminChain(17 个 Filter)
//          /api/public/** → PublicChain(3 个 Filter)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Spring Security 内置的 16+ Filter(部分):

WebAsyncManagerIntegrationFilter → SecurityContextPersistenceFilter
→ HeaderWriterFilter → CsrfFilter → LogoutFilter
→ UsernamePasswordAuthenticationFilter → DefaultLoginPageGeneratingFilter
→ ConcurrentSessionFilter → RememberMeAuthenticationFilter
→ AnonymousAuthenticationFilter → SessionManagementFilter
→ ExceptionTranslationFilter → FilterSecurityInterceptor
1
2
3
4
5
6

技术分析:

  • 动态链路匹配:不同 URL 路径匹配不同的 FilterChain——审批流 vs 公开接口用不同链;
  • Filter 可插拔:addFilterBefore/After 精准控制插入位置;
  • Spring 容器管理:Filter 的 Bean 生命周期由 Spring 管理——单例、线程安全有保障;
  • @Order 排序:多个 FilterChain 间的优先级由 @Order 控制。

# 4.6 4种实现速查表

实现方式 短路语义 环绕语义 链管理 框架依赖 推荐度
纯职责链 GoF ✅ 强力短路 ❌ 手动 next ✅ 零依赖 ⭐⭐⭐⭐
过滤链 Servlet Filter ✅ 可选短路 ✅ 支持 FilterChain 集中 需 Servlet ⭐⭐⭐⭐⭐
环绕链 OkHttp Interceptor ✅ 可选 ✅ 强力环绕 Chain 集中 需 OkHttp ⭐⭐⭐⭐⭐
Spring Security FilterChain ✅ 可选 ✅ 支持 Spring 容器 需 Spring Security ⭐⭐⭐⭐⭐

📌 一句话决策:审批/风控"命中即止"选纯职责链,Web 全量过滤选FilterChain,需 before/after 成对选环绕链,企业级安全选Spring Security FilterChain。

# 05.用前用后效果对比

为什么单独留一节做对比:很多人记住了"职责链"几个字,却没算过它到底"省"了多少。下面用 1.1 节 2300 万 GMV 事故做基准。

# 5.1 代码量 & 可维护性对比

// ❌ 用前:287 行 if-else
public ReviewResult review(LiveContent c) {
    if (containsSensitiveWord(c)) return reject(...);
    if (containsAdLawViolation(c)) return reject(...);
    // ... 287 行,5 个团队往里面塞
}

// ✅ 用后:5 行主流程 + 7 个 Handler 类 + 1 个注册中心
public ReviewResult review(LiveContent c) {
    return reviewChain.head().handle(c);   // 只接触链头
}
1
2
3
4
5
6
7
8
9
10
11

📊 13 维实测数据:

维度 ❌ 287 行 if-else(事故现场) ✅ 职责链 + 注册中心
主方法长度 287 行 5 行
团队协作 5 团队抢改一个文件 → git 冲突频发 各团队独立维护各自 Handler 类
规则插入 改主方法,破坏其他分支 → 事故根因 注册中心 register(handler, order),零侵入
规则顺序错位 287 行肉眼难查,金融违规被静默吃掉 链表结构线性可见,审计日志可回放
catch-all 短路误吃 事故根因 2 每节明确 toNext(),不会误吃
类型分支漏接入 直播口播未接虚假宣传 → 事故根因 3 Handler 接收统一 Context,所有类型都过链
灰度开关 if-else 里塞 if(featureFlag) 注册中心动态启用/禁用 Handler
单测可测性 287 行串成一坨 每个 Handler 独立单测
调试可追踪性 一个 review() 黑盒 链节点级 trace:进入/通过/拦截 全可见
新规则上线 改主代码,review 5 团队联签 新增 Handler 类,注册中心一行 register
规则版本管理 全靠 git history Handler 自带 version + author + 上线日期
监管审计 "为什么这条没拦"无法回答 链路追踪日志,每节决策有据可查
资损 封禁 14 天 + GMV 2300 万 + 赔偿 0

# 5.2 规则插队 & 顺序控制对比

// ❌ 用前:改顺序 = 改 287 行代码 + 肉眼检查 + 全量回归
// ✅ 用后:改配置一行,天级上线
ruleRegistry.register(new AntiFraudHandler(), order = 3);  // 插到第 3 位
1
2
3

📊 实测数据:

指标 用前 用后 差距
新增规则改动文件数 1 个核心文件 1 个新 Handler 类 改核心 → 只加新类
规则调序风险 可能破坏 287 行结构 改配置一行,零风险 手动→配置化
5 团队协作冲突率 每 Sprint 3+ 次 git 冲突 0 归零

# 5.3 核心收益

🔑 核心收益:职责链把"多步处理 + 顺序控制 + 短路决策"封装在独立的 Handler 对象中,通过 next 引用的链式结构递归传递——主流程只接触链头,新增/删除/调序只改装配。

结论:职责链模式的本质是 "把'多步处理 + 顺序敏感 + 可插拔 + 可灰度'的逻辑拆解为链节点,每节独立持有处理逻辑,自己决定'要不要拦截 / 要不要继续传',主流程只接触链头"。本次直播 GMV 2300 万的根因不是"开发不严谨"——而是 287 行 if-else 让 5 个团队互相破坏对方的规则,任何一个分支顺序错位都会让监管规则静默失效。改造为职责链后,287 行 → 5 行主流程 + 7 个 Handler 类 + 1 个注册中心,每条规则独立单测/灰度/版本/审计。

# 06.反面踩坑实录

为什么有这一节:01 节让你看到"不用职责链的痛",但职责链本身也不是银弹。

# 6.1 踩坑A:链节点忘记调 next → 请求丢失

【真实事故】 某支付系统订单审核链 8 节,第 3 节漏调 next 导致风控/反洗钱/限额全部跳过,1 周内被黑产薅走 230 万。

public class AuthHandler extends Handler {
    public Result handle(Request req) {
        if (!req.isLoggedIn()) return Result.fail("未登录");
        // ❌ 通过后忘记调 next.handle(req) → 请求在此结束
        return Result.ok();
    }
}
1
2
3
4
5
6
7

📌 教训:每个节点必须显式调用 next.handle(req) 或 toNext(req)。

✅ 正解:用模板方法封死——抽象类提供 final handle(),子类只实现 doHandle() 返回 PASS/REJECT,框架统一处理 next 调用。

# 6.2 踩坑B:链顺序错位 → 反向拦截

【真实事故】 某秒杀系统风控放在限流后,黑产用 1.2 万代理 IP 把限流额度耗尽,真实用户大面积秒杀失败,客诉量 2 小时涨 80 倍。

new RateLimitHandler().linkWith(new RiskHandler());  // ❌ 先限流后风控
1

📌 教训:拒绝率高的放前面、轻量本地校验先于查库、重的放最后。

✅ 正解:顺序三原则:① 拒绝率高的放前面;② 轻量(本地计算)先于重量(RPC/DB);③ 顺序固化在配置中心,上线必须 review。

# 6.3 踩坑C:链节点抛异常 → 整链中断

【真实事故】 库存预占 Handler 抛超时异常,已锁定的库存没人释放,30 分钟内库存被锁死,前台显示"售罄"。

public Result handle(Request req) {
    riskRpcClient.check(req);   // ❌ RPC 抛 TimeoutException → 整链挂
    return next.handle(req);
}
1
2
3
4

📌 教训:链节点内异常必须 catch,不能让链直接断。

✅ 正解:用 try-finally 保护 next 调用;链路框架统一异常包装为 Result.fail(),链继续走完清理节点。

# 6.4 踩坑D:链长度无上限 → 延迟雪崩

【真实事故】 某 API 网关 Filter 链 47 节,网关层 P99 延迟占整个请求 60%,业务投诉"网关比业务还慢"。

📌 教训:链长度硬上限(≤ 15 节),超长影响 P99。

✅ 正解:同类合并(5 个限流→1 个),异步化非关键节点(日志/审计异步发送),链路 trace 监控每节耗时。

# 6.5 踩坑E:环绕链 before/after 不成对

【真实事故】 OkHttp Interceptor 提前 return 导致 Span 未 close,Tracer 内存堆积 → Full GC → 服务降级。

public Response intercept(Chain chain) {
    log.info("before");
    if (request.isCanceled()) return Response.error();  // ❌ after 丢了!
    Response resp = chain.proceed(request);
    log.info("after");   // ← 永远走不到
    return resp;
}
1
2
3
4
5
6
7

📌 教训:环绕链 before/after 必须 try-finally 成对。

✅ 正解:Span span = start(); try { return proceed(); } finally { span.close(); }。

# 6.6 踩坑F:链节点共享可变状态

【真实事故】 限流 Handler 用实例变量计数,1.2 万 QPS 下计数器混乱,限流失效。

public class CounterHandler extends Handler {
    private int counter = 0;   // ❌ 单例多线程共享
    public Result handle(Request req) { counter++; return next.handle(req); }
}
1
2
3
4

📌 教训:Handler 必须无状态(单例),状态走外部存储。

✅ 正解:计数器/缓存用 ThreadLocal / ConcurrentHashMap / Redis;Code review 卡控:Handler 禁止非 final 实例变量。

# 6.7 开源案例速查 & 替代方案汇总

🔍 15 个真实开源/框架中的职责链

出处 链实现 节点类型 它解决了什么
Servlet FilterChain Filter.doFilter(req, resp, chain) 跨切面过滤器 J2EE 标准请求过滤模型
Spring Security 16+ Filter 串成 SecurityFilterChain Auth/CSRF/Session/Logout 企业级安全控制管线
Spring MVC HandlerInterceptor preHandle/postHandle/afterCompletion MVC 拦截器 控制器前后处理
OkHttp Interceptor Chain.proceed(request) 洋葱模型 网络拦截器 网络请求 + 缓存 + 重试 + 日志
Netty ChannelPipeline ChannelHandlerContext.fire***() 入站/出站事件 高性能网络事件流转
MyBatis Interceptor @Intercepts + Plugin.wrap SQL 插件链 分页/审计/监控/动态 SQL
Express.js middleware app.use((req, res, next) => next()) HTTP 中间件 Node.js Web 标准模式
Koa.js middleware async (ctx, next) => await next() 洋葱模型 异步 + Promise 风格中间件
gRPC Interceptor Unary/Stream Interceptor RPC 拦截器 跨语言 RPC 拦截标准
Dubbo Filter @Activate + Provider/Consumer Filter RPC 过滤器 服务治理(监控/限流/降级)
Apache Camel Pipeline EIP 路由处理器链 集成处理器 企业集成模式管线
Kafka Interceptor Producer/Consumer Interceptor 消息拦截器 消息发送/消费拦截
Tomcat Valve Pipeline.invoke() + Valve 链 请求处理阀 容器级请求处理
Logback/Log4j2 Filter decide(event) + ACCEPT/DENY/NEUTRAL 日志过滤器 日志事件分级过滤
JWT 鉴权链 / OAuth2 Filter 多种 AuthenticationProvider 认证链 多种认证方式串联

⚠️ 替代方案:什么时候不该用职责链

你的需求 推荐方案
步骤 ≤ 3 且永不增长 ✅ 直接 if-else,职责链是过度设计
每步必须全部执行(无短路) ✅ 管道/Stream:req.step1().step2().step3()
节点间有 DAG 依赖 ✅ 工作流引擎(Camunda/Activiti),非链表
需要并行执行多个节点 ✅ CompletableFuture/Reactor/Fork-Join
超低延迟(< 1ms) ✅ 直接 inline 调用,绕链有开销
节点数量高度动态 ✅ 注册中心 + 优先级队列

学习路径:先读 Servlet FilterChain.doFilter()(最朴素的链路传递)→ 再读 OkHttp RealInterceptorChain.proceed()(洋葱模型 30 行经典实现)→ 进阶读 Spring Security FilterChainProxy(16+ Filter + 动态匹配)→ 最后读 Netty DefaultChannelPipeline(高性能 + 入站/出站双向链)。

# 07.决策树与选型

经过前面 6 节的铺垫,是时候给一张能"贴在工位上"的决策图了。

# 7.1 该不该用职责链模式

flowchart TD
    Start(["我有 N 个处理步骤<br/>需要串行执行"]) --> Q1{"步骤数 ≥ 4 且<br/>预计持续新增?"}
    Q1 -->|否| Q1b{"步骤数 ≤ 3 且<br/>永不增长?"}
    Q1b -->|是| No1["❌ 直接 if-else<br/>职责链是过度设计"]
    Q1b -->|否| Q2{"每个步骤必须<br/>全部执行(无短路)?"}
    Q1 -->|是| Q2
    Q2 -->|是| Alt["⚠️ 考虑管道/Stream<br/>职责链不便表达全执行"]
    Q2 -->|否| Q3{"节点间有 DAG<br/>依赖关系?"}
    Q3 -->|是| Alt2["⚠️ 工作流引擎<br/>Camunda/Activiti"]
    Q3 -->|否| Q4{"需要多个团队<br/>独立维护不同节点?"}
    Q4 -->|是| Solution["✅ 职责链!<br/>每团队独立 Handler<br/>注册中心统一管理"]
    Q4 -->|否| Q5{"需要运行时<br/>动态调序/灰度?"}
    Q5 -->|是| Solution
    Q5 -->|否| Warn["⚠️ 可选职责链<br/>直接 if-else 也可"]

    style No1 fill:#fee
    style Alt fill:#ffe6cc
    style Alt2 fill:#ffe6cc
    style Warn fill:#ffe6cc
    style Solution fill:#dfd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 7.2 选哪种实现方式

如果决策树走到了"用职责链",再用下面这张图选具体实现:

flowchart TD
    Start([选择职责链的实现]) --> Q1{"需要 before/after<br/>环绕语义吗?"}
    Q1 -->|是| Q1b{"是 Web 场景?"}
    Q1b -->|是| FilterChain["✅ 过滤链 Servlet Filter<br/>或 Spring MVC Interceptor"]
    Q1b -->|否| Onion["✅ 环绕链/洋葱模型<br/>OkHttp Interceptor 风格<br/>proceed() 前后成对"]
    Q1 -->|否| Q2{"命中即止<br/>(审批流/风控)?"}
    Q2 -->|是| GoF["✅ 纯职责链 GoF 经典<br/>节点 toNext 控制传递<br/>null=短路返回"]
    Q2 -->|否| Q3{"Web 安全?"}
    Q3 -->|是| SpringSec["✅ Spring Security<br/>FilterChain 16+ Filter<br/>动态路径匹配"]
    Q3 -->|否| GoF

    style FilterChain fill:#e6ffe6
    style Onion fill:#fff4e6
    style GoF fill:#e6f3ff
    style SpringSec fill:#f0e6ff
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 7.3 选型清单速查

场景 该用吗 推荐方式
直播内容审核(7 规则 + 5 团队独立维护) ✅ 该用 纯职责链 GoF + 注册中心
下单校验链(6 道:登录→风控→库存→优惠→价格→防刷) ✅ 该用 纯职责链 GoF,装配式链
Web 请求 Filter(认证→日志→压缩→编码) ✅ 该用 过滤链 Servlet Filter
HTTP 客户端拦截(重试→缓存→日志) ✅ 该用 环绕链 OkHttp Interceptor
企业安全(16 个安全 Filter) ✅ 该用 Spring Security FilterChain
只有 2 步校验(登录 + 权限),永不增长 ❌ 别用 直接 if-else
ETL 数据管道(每步必须全过) ❌ 别用 Stream/管道模式
审批流(经理→总监→VP→CEO,金额分级命中即止) ✅ 该用 纯职责链 GoF
多个 Handler 需要并行执行 ❌ 别用 Fork-Join / CompletableFuture

# 08.总结与延伸

# 8.1 设计思想沉淀

回顾本篇 1 → 7 节的旅程,职责链模式真正教会我们的是这套思考模型:

阶段 学到了什么
01 案例引入 痛点是模式诞生的土壤——2300 万 GMV 的本质是 5 团队规则挤在 287 行 if-else,互相破坏
02 3次失败 if-else / List遍历 / 策略串联都不够——模式是从"试错"中收敛出来的
03 模式基础 四大硬约束:独立封装 / 短路控制 / 链式可视 / 接力传递
04 4种实现 实现差异本质是"短路 vs 全过 / 单向 vs 环绕 / 手动 vs 框架"的不同取舍
05 效果对比 数据说话:287 行→5 行,新增规则改核心→只加新类,5 团队冲突归零
06 反面踩坑 职责链不是免死金牌——忘调next/顺序错位/异常中断/链太长/不成对/共享状态
07 决策树 工程师的成熟度,不在于会写几种链,而在于知道"什么时候 if-else 就够了"

🔑 一句话核心:

职责链模式是通过 Handler 持有 next + 子类 doHandle 短路控制 + 链式装配 实现"多步接力处理"的模式,不是任何多步骤的万能药——步骤 ≤ 3 / 全量执行 / DAG 依赖 / 极致性能下,if-else / 管道 / 工作流才是更优解。

# 8.2 模式联动边界

职责链从来不是孤立存在的,它和其他模式有千丝万缕的关系:

flowchart LR
    Chain[职责链] -.一人处理.-> Strategy[策略:选一个]
    Chain -.层层嵌套.-> Decorator[装饰器:层层增强]
    Chain -.节点可封装.-> Command[命令:可撤销/缓存]
    Chain -.数据流式.-> Pipeline[管道:全量处理]
    Chain -.广播所有人.-> Observer[观察者:一对多通知]
1
2
3
4
5
6
模式 关系 一句话区别
职责链(Chain) 多人接力处理 任意节点可短路,沿链传递
策略(Strategy) 多人备选 选一个执行,平级互斥
装饰器(Decorator) 层层包装同一接口 不短路,逐层增强——环绕版职责链 ≈ 装饰器的动态运行期兄弟
命令(Command) 链节点可封装为 Command 调用对象化,可缓存/撤销/重放
管道(Pipeline) 数据流式处理 不短路,数据穿全程——职责链是控制流,管道是数据流
观察者(Observer) 事件广播 全部触发,一对多通知——职责链是接力,观察者是广播

一句话区分:

  • 多人接力 + 任意节点可短路 → 职责链;
  • 多人备选 + 选一执行 → 策略;
  • 层层包装 + 不短路 → 装饰器(环绕链 ≈ 装饰器的动态兄弟);
  • 数据流式 + 不短路 → 管道;
  • 调用对象化 + 可缓存可重放 → 命令;
  • 事件广播 + 多订阅者 → 观察者。

# 8.3 思考题与延伸

💭 三道思考题(建议手写答案,再对照回顾本文):

  1. Servlet FilterChain 为什么用 position 计数而不是链表 next 引用?(提示:回看 4.3——集中式管理 vs 分散式管理的权衡)
  2. 如果 2.3 节事故中的"金融营销违规"必须放在"广告法"之前执行,职责链如何实现?如果必须在"广告法"之后执行呢?(提示:回看 2.2 方案B 和 4.2——链装配的 then() 方法)
  3. OkHttp Interceptor 的 proceed() 为什么必须 try-finally 保护 around 语义?如果不能 try-finally 怎么办?(提示:回看 4.4 和 6.5——before/after 必须成对,否则资源泄漏)

📚 延伸阅读:

  • Servlet FilterChain.doFilter() 源码——最朴素的链路传递
  • OkHttp RealInterceptorChain.proceed() 源码——洋葱模型 30 行经典
  • Spring Security FilterChainProxy 源码——16+ Filter 动态匹配
  • Netty DefaultChannelPipeline 源码——高性能入站/出站双向事件链

上一篇 迭代器模式设计思想 → 本篇 → 下一篇:命令模式设计思想——把"调用"也变成对象。

上次更新: 2026/06/17, 11:43:57
迭代器模式设计思想
命令模式设计思想

← 迭代器模式设计思想 命令模式设计思想→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式