编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • 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
  • C语言入门精通

  • Cpp入门到精通

  • Java入门精通

    • README
    • 入门教程

    • 综合案例

    • 专栏博客

      • README
      • JVM内存模型与对象
      • 类加载与双亲委派
      • 垃圾回收与GC调优
      • 异常体系与JVM机制
      • 字节码指令集javap实战
      • JIT编译与去优化机制
      • JVM性能诊断工具链
      • OOM八大现场全景剖析
      • JVM参数调优全景图
      • GraalVM与AOT编译原理
      • HashMap底层哈希设计
      • String不可变与常量池
      • ArrayList与LinkedList源码
      • ConcurrentHashMap并发
      • TreeMap与红黑树原理
      • LinkedHashMap与LRU实现
      • Java数字类型原理
      • Object通用方法的契约
      • 泛型擦除与类型系统
      • 枚举原理与最佳实践
      • 注解原理与编译期处理
      • Lambda与引用底层原理
      • Stream原理与流水线设计
      • Optional设计原理
      • Record密封类与模式
      • 反射机制与动态代理
      • MethodHandle与VarHandle
      • 三大字节码框架对比
      • JavaAgent与Instrumentation机制
      • AOP三种实现路线对比
      • synchronized与锁升级
      • volatile与JMM内存模型
      • 线程池核心源码设计
      • Thread线程生命周期
      • AQS同步框架源码
      • 并发锁三剑客
      • CAS和Atomic深入分析
      • 五大同步器对比
      • CompletableFuture异步
      • IO模型演进BIO到AIO
      • ByteBuffer与堆外内存
      • 序列化原理与替代方案
      • 文件IO与NIO.2
      • 面向对象的真意
      • JDK设计模式上
      • JDK设计模式下
        • 1. 案例引入
          • 1.1 一段反常代码
          • 1.2 顺藤摸到根因
          • 1.3 我们要回答什么
        • 2. 架构概览
          • 2.1 行为型十一模式
          • 2.2 为什么这么切
        • 3. 迭代器模式
          • 3.1 集合内外两种遍历
          • 3.2 Iterator与fail-fast
          • 3.3 ListIterator的双向
          • 3.4 Spliterator与并行
          • 3.5 内迭代forEach对比
        • 4. 观察者模式
          • 4.1 推送与拉取两派
          • 4.2 Observable的废与立
          • 4.3 PropertyChangeSupport
          • 4.4 Flow响应式四角色
          • 4.5 EventBus与解耦边界
        • 5. 模板方法再深挖
          • 5.1 骨架与钩子
          • 5.2 AQS的acquire骨架
          • 5.3 AbstractList的复用
          • 5.4 Servlet的service分发
        • 6. 策略模式
          • 6.1 if-else长龙的退场
          • 6.2 Comparator策略族
          • 6.3 RejectedExecutionHandler
          • 6.4 函数式接口的解放
        • 7. 责任链模式
          • 7.1 链表与递归两种实现
          • 7.2 ServletFilter的pre-post
          • 7.3 Netty ChannelPipeline
          • 7.4 OkHttp Interceptor
          • 7.5 链的中断与短路
        • 8. 命令与状态
          • 8.1 Runnable即命令
          • 8.2 撤销与命令日志
          • 8.3 状态机消除if地狱
          • 8.4 Thread.State状态实例
        • 9. 备忘录与解释器
          • 9.1 备忘录的封装边界
          • 9.2 事务回滚的备忘录影
          • 9.3 解释器与Pattern
          • 9.4 何时不用解释器
        • 10. 综合案例串讲
          • 10.1 案例真相揭晓
          • 10.2 一个请求的旅行
          • 10.3 设计哲学回扣
          • 10.4 速查表
        • 卷七收官与全册尾声
      • SPI与模块化设计
  • Go入门到精通

  • JavaScript入门

  • CodeX
  • Java入门精通
  • 专栏博客
杨充
2026-06-02
目录

JDK设计模式下

# 46.JDK设计模式下

# 目录介绍

  • 1. 案例引入
    • 1.1 一段反常代码
    • 1.2 顺藤摸到根因
    • 1.3 我们要回答什么
  • 2. 架构概览
    • 2.1 行为型十一模式
    • 2.2 为什么这么切
  • 3. 迭代器模式
    • 3.1 集合内外两种遍历
    • 3.2 Iterator与fail-fast
    • 3.3 ListIterator的双向
    • 3.4 Spliterator与并行
    • 3.5 内迭代forEach对比
  • 4. 观察者模式
    • 4.1 推送与拉取两派
    • 4.2 Observable的废与立
    • 4.3 PropertyChangeSupport
    • 4.4 Flow响应式四角色
    • 4.5 EventBus与解耦边界
  • 5. 模板方法再深挖
    • 5.1 骨架与钩子
    • 5.2 AQS的acquire骨架
    • 5.3 AbstractList的复用
    • 5.4 Servlet的service分发
  • 6. 策略模式
    • 6.1 if-else长龙的退场
    • 6.2 Comparator策略族
    • 6.3 RejectedExecutionHandler
    • 6.4 函数式接口的解放
  • 7. 责任链模式
    • 7.1 链表与递归两种实现
    • 7.2 ServletFilter的pre-post
    • 7.3 Netty ChannelPipeline
    • 7.4 OkHttp Interceptor
    • 7.5 链的中断与短路
  • 8. 命令与状态
    • 8.1 Runnable即命令
    • 8.2 撤销与命令日志
    • 8.3 状态机消除if地狱
    • 8.4 Thread.State状态实例
  • 9. 备忘录与解释器
    • 9.1 备忘录的封装边界
    • 9.2 事务回滚的备忘录影
    • 9.3 解释器与Pattern
    • 9.4 何时不用解释器
  • 10. 综合案例串讲
    • 10.1 案例真相揭晓
    • 10.2 一个请求的旅行
    • 10.3 设计哲学回扣
    • 10.4 速查表

# 1. 案例引入

# 1.1 一段反常代码

我们接手了一个企业级"订单审批工作流"模块,代码在新人接手时连续出了 7 个问题——这些问题恰好覆盖了行为型模式的几乎所有典型反例:

// 模块 1:审批流引擎
public class ApprovalEngine {
    
    public void approve(Order order, User user) {
        // ★ 一长串 if-else 控制流
        if (order.getAmount() < 1000) {
            // 小额:组长审批
            if (user.getRole().equals("LEADER")) {
                order.setStatus("APPROVED");
            }
        } else if (order.getAmount() < 10000) {
            // 中额:经理审批
            if (user.getRole().equals("MANAGER")) {
                order.setStatus("APPROVED");
            }
        } else if (order.getAmount() < 100000) {
            // 大额:总监 + 财务双签
            if (user.getRole().equals("DIRECTOR")) {
                order.setStatus("DIRECTOR_APPROVED");
            } else if (user.getRole().equals("CFO") &&
                       order.getStatus().equals("DIRECTOR_APPROVED")) {
                order.setStatus("APPROVED");
            }
        } else {
            // 巨额:CEO 审批
            if (user.getRole().equals("CEO")) {
                order.setStatus("APPROVED");
            }
        }
        // ... 还有撤销、加签、转交等 6 个分支
    }
}

// 模块 2:审批结果通知
public class ApprovalNotifier {
    private List<Listener> listeners = new ArrayList<>();
    
    public void addListener(Listener l) { listeners.add(l); }
    
    public void onApproved(Order o) {
        for (Listener l : listeners) {           // ★ 同步串行遍历
            try {
                l.onEvent(o);
            } catch (Exception e) {
                throw new RuntimeException(e);   // ★ 一个监听器抛异常 阻断后续
            }
        }
    }
}

// 模块 3:审批历史遍历
public List<String> collectHistory(Order order) {
    List<ApprovalRecord> records = order.getRecords();
    List<String> result = new ArrayList<>();
    Iterator<ApprovalRecord> it = records.iterator();
    while (it.hasNext()) {
        ApprovalRecord r = it.next();
        result.add(r.getDesc());
        if (r.isInvalid()) {
            records.remove(r);                   // ★ 遍历中直接修改集合
        }
    }
    return result;
}

// 模块 4:状态切换
public void changeStatus(Order order, String action) {
    String s = order.getStatus();
    if (s.equals("DRAFT") && action.equals("SUBMIT")) {
        order.setStatus("SUBMITTED");
    } else if (s.equals("SUBMITTED") && action.equals("APPROVE")) {
        order.setStatus("APPROVED");
    } else if (s.equals("APPROVED") && action.equals("CANCEL")) {
        order.setStatus("CANCELLED");
    }
    // ★ 实际有 12 个状态 × 8 个 action = 96 个分支……
}

// 模块 5:撤销订单
public void undoLastOperation(Order order) {
    // ★ 直接拷贝 5 个字段,但漏了关联的 attachments
    Order snapshot = new Order();
    snapshot.setStatus(order.getStatus());
    snapshot.setAmount(order.getAmount());
    snapshot.setApprover(order.getApprover());
    snapshot.setUpdateTime(order.getUpdateTime());
    snapshot.setRemark(order.getRemark());
    // attachments 漏了……回滚后附件状态错乱
}
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

# 1.2 顺藤摸到根因

上线后 5 个真实事故陆续暴露:

  • 现象 ①——ApprovalEngine.approve 中 if-else 越加越多,每加一个审批规则就要改这个核心方法,老规则被改坏的事故出过 3 次。
  • 现象 ②——ApprovalNotifier.onApproved 中第一个监听器抛了 NullPointerException,后面的 5 个监听器全部没执行——审计日志没写、消息没发、库存没释放,半夜 P0 告警。
  • 现象 ③——collectHistory 在 iterator 中调 records.remove(r),运行时随机抛 ConcurrentModificationException——为什么"随机"?为什么有时候不抛?
  • 现象 ④——changeStatus 的 12 × 8 个 if 分支,有一次新加状态忘记加 else if,订单可以从 CANCELLED 直接跳回 APPROVED——业务规则被绕过。
  • 现象 ⑤——undoLastOperation 写法太糙,每加一个字段就忘改快照,撤销后数据状态和日志对不上。
  • 现象 ⑥——审批流程同时还面临"日志、鉴权、限流、缓存、事务"5 道横切关注点,每一道都要嵌入 approve 方法本体——代码全是模板。
  • 现象 ⑦——业务方说:"我能不能像 OkHttp Interceptor 那样配置一条审批链",工程师听不懂——什么是责任链?

把这些现象串起来,至少 8 个行为型模式核心问题:

① if-else 长龙 → 怎么用策略模式收编?                  → 第6章
② 监听器异常阻断、同步阻塞 → 观察者怎么解?            → 第4章
③ ConcurrentModificationException 是怎么探测的?        → 第3章
④ 12×8 状态分支 → 状态机怎么消除?                     → 第8.3
⑤ 一锤子拷贝快照 → 备忘录模式的封装边界                → 第9章
⑥ 横切关注点泛滥 → 责任链 / 模板方法分别什么场景?     → 第5、7章
⑦ 客户端能否像配 OkHttp 那样配审批链?                 → 第7.4
1
2
3
4
5
6
7

# 1.3 我们要回答什么

第 49 篇我们讲了创建型 + 结构型 8 模式。本篇收官,把 GoF 行为型 11 模式中 JDK 真实有范本的 8 个全部讲透,以及把第 48 篇就开始的"组合优于继承"主线推到收口。

反例 → 模式公式 → JDK 真实范本 → 适用边界与陷阱
1

本篇路线:

flowchart LR
    A[行为型] --> B[迭代器 第3章]
    A --> C[观察者 第4章]
    A --> D[模板方法 第5章]
    A --> E[策略 第6章]
    A --> F[责任链 第7章]
    A --> G[命令+状态 第8章]
    A --> H[备忘录+解释器 第9章]
    B & C & D & E & F & G & H --> I[案例串讲 第10章]
1
2
3
4
5
6
7
8
9

# 2. 架构概览

# 2.1 行为型十一模式

GoF 行为型共 11 个,本篇覆盖在 JDK 中有显式范本的 9 个(不重点讲的解释器与中介者放第 9 章顺带提):

┌──────────────────────────────────────────────────────────────┐
│  行为型 Behavioral:解决"对象之间如何协作"                    │
│  ├─ 迭代器  Iterator      :统一遍历协议         ★第3章       │
│  ├─ 观察者  Observer      :松耦合通知           ★第4章       │
│  ├─ 模板方法 Template     :骨架不变细节可填     ★第5章       │
│  ├─ 策略    Strategy      :可替换的算法簇       ★第6章       │
│  ├─ 责任链  Chain         :流水线式分发         ★第7章       │
│  ├─ 命令    Command       :请求对象化           ★第8章       │
│  ├─ 状态    State         :状态驱动行为         ★第8章       │
│  ├─ 备忘录  Memento       :保存/恢复内部状态    ★第9章       │
│  ├─ 解释器  Interpreter   :DSL 求值            ★第9章       │
│  ├─ 中介者  Mediator      :M:N 通信收敛         (略)        │
│  └─ 访问者  Visitor       :双分派              (第48篇已讲)│
└──────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 2.2 为什么这么切

疑惑:为什么行为型模式数量最多(11 个),而结构型只有 7 个、创建型只有 5 个?

论证:

  1. "行为"维度更多——对象一旦造出来、连起来,剩下要做的事都是"协作"。协作的形式天然丰富:单向通知(观察者)、双向交互(中介者)、流水线(责任链)、可替换算法(策略)……
  2. "运行时变化"是行为型的主战场——创建型管"造",结构型管"连",行为型管"变"。变化点越多的系统(比如审批流、网关、规则引擎)越需要行为型模式。
  3. 最容易被语言机制取代——Java 8 引入 Lambda 后,策略、命令、观察者三个模式的样板代码都被一口气抹平了。

结论:行为型模式之所以多,是因为"对象协作"本身存在大量正交的维度——通知、流水线、状态、撤销、遍历……每个维度都需要独立的模式来切分。

flowchart TB
    A[行为型11模式] --> B[一对多通知<br/>观察者]
    A --> C[流水线分发<br/>责任链 / 中介者]
    A --> D[算法可替换<br/>策略 / 模板方法]
    A --> E[请求对象化<br/>命令]
    A --> F[状态驱动<br/>状态]
    A --> G[操作可追溯<br/>备忘录 / 解释器]
    A --> H[访问解耦<br/>访问者 / 迭代器]
1
2
3
4
5
6
7
8

# 3. 迭代器模式

迭代器解决一件事:让客户端"按顺序访问聚合对象的元素",但不暴露内部表示。

# 3.1 集合内外两种遍历

// 1) 外部迭代(客户端控制)
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();           // ★ 客户端推动迭代
    if (filter(s)) ...
}

// 2) 内部迭代(集合自己控制)
list.forEach(s -> {                  // ★ 集合内部推动
    if (filter(s)) ...
});
1
2
3
4
5
6
7
8
9
10
11

对比:

维度 外部迭代(Iterator) 内部迭代(forEach/Stream)
控制权 客户端 集合自身
灵活度 高(可中断、可逆向) 低(基本只能向前)
并行化 困难(状态在客户端) 容易(Stream.parallel)
性能 中等 高(JIT 易于内联)

# 3.2 Iterator与fail-fast

public interface Iterator<E> {
    boolean hasNext();
    E next();
    default void remove() { throw new UnsupportedOperationException(); }
    default void forEachRemaining(Consumer<? super E> action) { ... }
}
1
2
3
4
5
6

关键问题:Iterator 是怎么探测"边迭代边修改"的?

回到第 1 章现象 ③——collectHistory 中 records.remove(r) 抛 ConcurrentModificationException。

论证:以 ArrayList.Itr 为例:

// ArrayList.java (JDK 21)
private class Itr implements Iterator<E> {
    int cursor;                                  // 下一个元素位置
    int lastRet = -1;
    int expectedModCount = modCount;             // ★ 创建迭代器时记录的版本号
    
    public E next() {
        checkForComodification();                // ★ 每次 next 都检查
        // ... 返回元素
    }
    
    final void checkForComodification() {
        if (modCount != expectedModCount)        // ★ 不一致就抛
            throw new ConcurrentModificationException();
    }
}

public boolean remove(Object o) {
    // ...
    fastRemove(es, i);                           // 内部 modCount++
    return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

机制:

迭代器创建时    : expectedModCount = modCount = 5
list.remove(x)  : modCount = 6
iterator.next() : modCount(6) != expectedModCount(5) → 抛 CME
1
2
3

为什么"随机"抛?——其实不随机。如果你在迭代到最后一个元素之后才修改,下次进 next() 时 hasNext() 已经返回 false,根本不会触发 checkForComodification,CME 就不会抛——所以表面看像"运气问题"。

正确写法:

// 用迭代器自己的 remove
Iterator<ApprovalRecord> it = records.iterator();
while (it.hasNext()) {
    ApprovalRecord r = it.next();
    if (r.isInvalid()) {
        it.remove();                              // ★ 安全:内部同步 modCount
    }
}

// 或 JDK 8+ 的 removeIf
records.removeIf(ApprovalRecord::isInvalid);
1
2
3
4
5
6
7
8
9
10
11

结论:fail-fast 不是线程安全机制,是编程错误探测器。它用 modCount 这个版本号在迭代过程中对账,一旦对账失败立刻报错——让 bug 死在最近的现场。

# 3.3 ListIterator的双向

public interface ListIterator<E> extends Iterator<E> {
    boolean hasPrevious();
    E previous();
    int nextIndex();
    int previousIndex();
    void set(E e);
    void add(E e);
}
1
2
3
4
5
6
7
8

ListIterator 在 Iterator 基础上加了双向遍历 + 原地修改。它解决了 Iterator 单向 + 只读的局限——但代价是只能用在 List(需要可定位)上。

# 3.4 Spliterator与并行

JDK 8 引入 Spliterator(splittable iterator)——可分割的迭代器:

public interface Spliterator<T> {
    boolean tryAdvance(Consumer<? super T> action);   // 类似 hasNext+next
    Spliterator<T> trySplit();                         // ★ 把自己切一半返回
    long estimateSize();
    int characteristics();                             // ORDERED/SORTED/SIZED/...
}
1
2
3
4
5
6
flowchart TD
    A[原 Spliterator<br/>0..1000] --> B[trySplit]
    B --> C[左半 0..500]
    B --> D[右半 500..1000]
    C --> E[trySplit]
    D --> F[trySplit]
    E --> G[0..250]
    E --> H[250..500]
    F --> I[500..750]
    F --> J[750..1000]
    G & H & I & J --> K[ForkJoinPool 各线程并行处理]
1
2
3
4
5
6
7
8
9
10
11

Stream.parallelStream() 内部就是用 Spliterator.trySplit() 把数据切片喂给 ForkJoinPool。这是迭代器模式从"单线程顺序遍历"演进到"并行分治"的现代形态。

# 3.5 内迭代forEach对比

JDK 8+ 的 forEach 是内迭代——把"怎么走"封装在集合内部:

default void forEach(Consumer<? super T> action) {
    for (T t : this) action.accept(t);
}
1
2
3

但要记住:forEach 中改集合一样抛 CME——内迭代不是免责金牌,只是把循环倒过来写而已。

# 4. 观察者模式

# 4.1 推送与拉取两派

┌─────────────────────┐                  ┌─────────────────────┐
│  Subject 主题        │ ─── 通知 ───►   │  Observer 观察者     │
│  - List<Observer>   │                  │  + update(...)      │
│  + register/notify  │                  └─────────────────────┘
└─────────────────────┘
         │
         ├── 推(push):把数据塞给观察者     update(Event e)
         └── 拉(pull):观察者反向取数据     update(Subject s)
1
2
3
4
5
6
7
8

推送简单但耦合高(事件结构改了所有观察者都要改);拉取解耦但调用复杂。实战通常推主要数据 + 留个 source 引用让观察者按需拉。

# 4.2 Observable的废与立

JDK 1.0 就内置了 java.util.Observable / Observer:

// JDK 1.0 ~ 8
public class Observable {
    private Vector<Observer> obs = new Vector<>();
    public void notifyObservers(Object arg) {
        for (Observer o : obs) o.update(this, arg);
    }
}

@Deprecated(since = "9")
public interface Observer {
    void update(Observable o, Object arg);
}
1
2
3
4
5
6
7
8
9
10
11
12

JDK 9 标记 @Deprecated 的原因官方写得很直白:

  1. 不可继承的限制——Observable 是类不是接口,子类只能 extends 它,违反第 48 篇的"组合优于继承"。
  2. 类型不安全——参数是 Object,强转满天飞。
  3. 顺序不保证 + 不支持失败处理——异常处理粗糙,回到现象 ②。
  4. 不支持反压——下游慢了,上游照样推,必爆 OOM。

继任者:java.util.concurrent.Flow(响应式四接口,Reactor/RxJava 的最小公倍数)。

# 4.3 PropertyChangeSupport

java.beans.PropertyChangeSupport 是 GUI/JavaBean 时代的观察者——至今仍能用:

public class Order {
    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
    private String status;
    
    public void setStatus(String newVal) {
        String old = this.status;
        this.status = newVal;
        pcs.firePropertyChange("status", old, newVal);    // ★ 通知
    }
    
    public void addPropertyChangeListener(PropertyChangeListener l) {
        pcs.addPropertyChangeListener(l);
    }
}

// 客户端
order.addPropertyChangeListener(evt -> {
    if (evt.getPropertyName().equals("status")) {
        System.out.println("状态变了:" + evt.getOldValue() + "→" + evt.getNewValue());
    }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

它解决了 Observable 的两个问题:类型安全(PropertyChangeEvent)+ 按字段过滤。但仍然是同步推送,仍不支持反压。

# 4.4 Flow响应式四角色

JDK 9 引入 java.util.concurrent.Flow——这是 Reactive Streams 规范在 JDK 中的官方落地:

public final class Flow {
    public interface Publisher<T>     { void subscribe(Subscriber<? super T> s); }
    public interface Subscriber<T>    {
        void onSubscribe(Subscription s);
        void onNext(T item);
        void onError(Throwable t);
        void onComplete();
    }
    public interface Subscription     { void request(long n); void cancel(); }
    public interface Processor<T,R> extends Publisher<R>, Subscriber<T> {}
}
1
2
3
4
5
6
7
8
9
10
11
sequenceDiagram
    participant P as Publisher
    participant S as Subscriber
    P->>S: onSubscribe(subscription)
    S->>P: subscription.request(N)
    Note over P: 我有数据可以推 N 个
    P->>S: onNext(item1)
    P->>S: onNext(item2)
    Note over S: 处理慢了
    P->>S: onNext(itemN)
    Note over S: 处理完一批再 request
    S->>P: subscription.request(M)
    P->>S: onComplete()
1
2
3
4
5
6
7
8
9
10
11
12
13

Flow 的关键创新:反压(backpressure)——Subscriber 通过 subscription.request(n) 告诉上游"我能接 n 个",上游推超过就违约。这一招把"快生产慢消费导致的 OOM"从根上掐死。

JDK 自带的 SubmissionPublisher 是 Flow.Publisher 的开箱即用实现:

SubmissionPublisher<Order> publisher = new SubmissionPublisher<>();
publisher.subscribe(new Flow.Subscriber<>() {
    private Flow.Subscription sub;
    public void onSubscribe(Flow.Subscription s) { 
        this.sub = s; sub.request(1);     // ★ 一次只要一个
    }
    public void onNext(Order o) {
        process(o); 
        sub.request(1);                    // ★ 处理完再要下一个
    }
    public void onError(Throwable t) {}
    public void onComplete() {}
});
publisher.submit(order);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

回到现象 ②——异常阻断后续监听器的根因是 for + try/catch + throw。Flow 的解法:每个 Subscriber 独立线程 + 独立异常隔离,一个挂了不影响别人。

# 4.5 EventBus与解耦边界

工程实战还有 Guava EventBus、Spring ApplicationEventPublisher——它们都是观察者的工业级落地,特点是:

  • 注册靠注解(@Subscribe / @EventListener),不写 addListener
  • 异步派发可选(@Async / AsyncEventBus)
  • 消息按类型路由,自动过滤

但要警惕 EventBus 滥用——它把代码调用关系藏到了运行时反射查找里,断点 debug 极困难。模块边界(域之间)用 EventBus,模块内部不用——这是工程上的红线。

# 5. 模板方法再深挖

第 48 篇我们提过 AbstractList 是模板方法的范本,本节把它放到 AQS 这个更"硬核"的范本上展开。

# 5.1 骨架与钩子

模板方法的精髓两条:

  1. 骨架方法(template)声明 final——不允许子类破坏流程
  2. 钩子方法(hook)声明 abstract 或 protected——子类按需实现
abstract class Beverage {
    // 骨架(final 锁死)
    public final void prepare() {
        boilWater();           // 共用步骤
        brew();                // ★ 钩子:泡 / 冲
        pourInCup();
        addCondiments();       // ★ 钩子:加什么调料
    }
    private void boilWater() { /* 共用 */ }
    private void pourInCup() { /* 共用 */ }
    protected abstract void brew();
    protected abstract void addCondiments();
}
class Tea extends Beverage { 
    protected void brew() { /* 泡茶 */ }
    protected void addCondiments() { /* 加柠檬 */ }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 5.2 AQS的acquire骨架

AbstractQueuedSynchronizer 是 JDK 中模板方法的最高范本(详见第 36 篇):

// AQS 框架方法(骨架,子类不该覆盖)
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&                          // ★ 钩子 1:尝试获取
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {                           // ★ 钩子 2:尝试释放
        Node h = head;
        if (h != null && h.waitStatus != 0) unparkSuccessor(h);
        return true;
    }
    return false;
}

// 钩子(子类填)
protected boolean tryAcquire(int arg)       { throw new UnsupportedOperationException(); }
protected boolean tryRelease(int arg)       { throw new UnsupportedOperationException(); }
protected int     tryAcquireShared(int arg) { throw new UnsupportedOperationException(); }
protected boolean tryReleaseShared(int arg) { throw new UnsupportedOperationException(); }
protected boolean isHeldExclusively()       { throw new UnsupportedOperationException(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
flowchart TB
    A[acquire 骨架方法 final] --> B[调用 tryAcquire 钩子]
    B --> C{钩子返回}
    C -->|成功 true| D[结束]
    C -->|失败 false| E[addWaiter 入队]
    E --> F[acquireQueued 自旋+park]
    F --> D
    
    G[ReentrantLock] -.填写.-> B
    H[Semaphore] -.填写.-> B
    I[CountDownLatch] -.填写.-> B
1
2
3
4
5
6
7
8
9
10
11

AQS 的伟大就在于:把"队列管理 + 阻塞唤醒 + CAS 状态机"这些复杂细节锁死在骨架里,子类只填 5~6 行 tryAcquire 即可造出 ReentrantLock、Semaphore、CountDownLatch、ReadWriteLock 等所有同步器——这是模板方法的极致表达。

# 5.3 AbstractList的复用

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
    public boolean add(E e) {
        add(size(), e);                              // ★ 骨架:调用 add(int, E)
        return true;
    }
    public abstract E get(int index);                // ★ 钩子
    public E set(int index, E element) { throw new UnsupportedOperationException(); }
    public void add(int index, E element) { throw new UnsupportedOperationException(); }
    public abstract int size();                      // ★ 钩子
    
    public Iterator<E> iterator() {                  // 骨架:基于 get + size 实现
        return new Itr();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

ArrayList 只需要填 get / set / add(int,E) / remove(int) / size,剩下 addAll / iterator / contains / indexOf / subList ... 全部由 AbstractList 的模板方法提供。这是"复用 + 强制契约"的范例。

# 5.4 Servlet的service分发

// HttpServlet.java
public abstract class HttpServlet extends GenericServlet {
    protected void service(HttpServletRequest req, HttpServletResponse resp) {
        String method = req.getMethod();
        if      (method.equals("GET"))    doGet(req, resp);     // ★ 钩子
        else if (method.equals("POST"))   doPost(req, resp);    // ★ 钩子
        else if (method.equals("PUT"))    doPut(req, resp);     // ★ 钩子
        else if (method.equals("DELETE")) doDelete(req, resp);  // ★ 钩子
        // ...
    }
    
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
    }
    // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

写 Servlet 时,我们继承 HttpServlet 只覆盖需要的 doGet/doPost——HTTP 方法分发的骨架被 service 锁死。Spring MVC 的 DispatcherServlet 在这之上又加了一层——每个请求经过 HandlerMapping → HandlerAdapter → ViewResolver,每一步都是钩子。

# 6. 策略模式

# 6.1 if-else长龙的退场

回到第 1 章现象 ①——ApprovalEngine.approve 那个 if-else 长龙,每加一种规则就改核心方法。策略模式公式:

1. 一个策略接口(Strategy)
2. N 个具体策略实现(ConcreteStrategy)
3. 一个上下文(Context)持有策略引用,按需调用
1
2
3

重构:

// 1) 策略接口
public interface ApprovalStrategy {
    boolean canApprove(Order order, User user);
    void doApprove(Order order, User user);
}

// 2) 具体策略
public class SmallAmountStrategy implements ApprovalStrategy {
    public boolean canApprove(Order o, User u) {
        return o.getAmount() < 1000 && "LEADER".equals(u.getRole());
    }
    public void doApprove(Order o, User u) { o.setStatus("APPROVED"); }
}
public class MediumAmountStrategy implements ApprovalStrategy { /* 经理 */ }
public class LargeAmountStrategy  implements ApprovalStrategy { /* 总监+CFO */ }
public class HugeAmountStrategy   implements ApprovalStrategy { /* CEO */ }

// 3) 注册到 Map(避免又一坨 if-else)
public class ApprovalEngine {
    private final List<ApprovalStrategy> strategies = List.of(
        new SmallAmountStrategy(),
        new MediumAmountStrategy(),
        new LargeAmountStrategy(),
        new HugeAmountStrategy());
    
    public void approve(Order order, User user) {
        strategies.stream()
            .filter(s -> s.canApprove(order, user))
            .findFirst()
            .ifPresent(s -> s.doApprove(order, user));
    }
}
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

新增规则只加一个新类——ApprovalEngine 不动。这就是 OCP(开闭原则)的落地。

# 6.2 Comparator策略族

JDK 中策略模式最经典的范本:Comparator。

// 排序算法是固定的(TimSort),但"怎么比"是策略
List<Order> orders = ...;
orders.sort(Comparator.comparing(Order::getAmount));                    // 按金额
orders.sort(Comparator.comparing(Order::getCreateTime).reversed());     // 按时间倒序
orders.sort(Comparator.comparingInt(Order::getPriority)
                      .thenComparing(Order::getId));                    // 多键策略组合
1
2
3
4
5
6

Comparator 不只是策略——它还是策略组合子(combinator):reversed()、thenComparing()、nullsFirst() 都返回新的策略,老策略不变。这是函数式 + 策略模式的混血儿,详见第 48 篇 8.5 节。

# 6.3 RejectedExecutionHandler

ThreadPoolExecutor 的拒绝策略是另一个 JDK 范本:

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

// 4 种现成策略
new ThreadPoolExecutor.AbortPolicy();          // 抛异常(默认)
new ThreadPoolExecutor.DiscardPolicy();        // 静默丢弃
new ThreadPoolExecutor.DiscardOldestPolicy();  // 丢最老 + 重试
new ThreadPoolExecutor.CallerRunsPolicy();     // 调用者线程自己跑
1
2
3
4
5
6
7
8
9

调用者(ThreadPoolExecutor)不知道也不关心具体哪种策略——只调 handler.rejectedExecution(r, this)。这是策略模式"调用者与算法解耦"的教科书例子。

# 6.4 函数式接口的解放

JDK 8 之前的策略模式要写一堆类,JDK 8 之后大量被 Function/Predicate/Consumer/Supplier 等函数式接口直接代替:

// 老写法:写一个 implements Comparator 的类
class AmountComparator implements Comparator<Order> {
    public int compare(Order a, Order b) { return Long.compare(a.getAmount(), b.getAmount()); }
}

// JDK 8+:一行 Lambda
Comparator<Order> c = (a, b) -> Long.compare(a.getAmount(), b.getAmount());

// 更简:方法引用
Comparator<Order> c2 = Comparator.comparingLong(Order::getAmount);
1
2
3
4
5
6
7
8
9
10

结论:Java 8+ 的策略模式 = 函数式接口 + Lambda——不再需要写显式类。但是当策略有状态(字段)或需要多个方法时,仍然回到显式接口 + 类。

# 7. 责任链模式

责任链:请求沿一条链传递,每个节点决定是处理、传给下一个,还是中断。

# 7.1 链表与递归两种实现

// 实现 1:链表式(每个 Handler 持有 next)
abstract class Handler {
    protected Handler next;
    public Handler setNext(Handler n) { this.next = n; return n; }
    public abstract void handle(Request req);
}

// 实现 2:递归式(链作为参数传递,pre-post 都能拦截)
interface Filter {
    void doFilter(Request req, FilterChain chain);
}
interface FilterChain {
    void doFilter(Request req);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

链表式简单但只能 pre 拦截(处理之前能干嘛,下游处理之后我什么都做不了);递归式复杂但pre 和 post 都能拦截(典型如 Servlet Filter)。

# 7.2 ServletFilter的pre-post

public class AuthFilter implements Filter {
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
        // ★ pre 阶段:调下游前
        if (!authorize(req)) {
            ((HttpServletResponse) resp).sendError(401);
            return;                                 // ★ 中断链
        }
        long start = System.currentTimeMillis();
        
        chain.doFilter(req, resp);                  // ★ 调下游
        
        // ★ post 阶段:调下游之后
        log.info("耗时 {}ms", System.currentTimeMillis() - start);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
请求方向 →
┌────────┐   ┌────────┐   ┌────────┐   ┌────────┐
│Filter1 │ → │Filter2 │ → │Filter3 │ → │Servlet │
│(pre)   │   │(pre)   │   │(pre)   │   │ 真处理  │
│        │ ← │        │ ← │        │ ← │        │
│(post)  │   │(post)  │   │(post)  │   │        │
└────────┘   └────────┘   └────────┘   └────────┘
                ← 响应方向
1
2
3
4
5
6
7
8

核心机制:每个 Filter 在 chain.doFilter() 之前做 pre 工作,之后做 post 工作——天然形成"洋葱模型"。

# 7.3 Netty ChannelPipeline

Netty 的 ChannelPipeline 是责任链模式的另一个工业级范本,但更精细——把链拆成入站(Inbound)和出站(Outbound)两条方向相反的链:

入站事件 (channelRead) →
  HeadContext → Decoder → BizHandler → TailContext

出站事件 (write/flush) ←
  HeadContext ← Encoder ← BizHandler ← TailContext
1
2
3
4
5

每个 ChannelHandler 决定要不要 ctx.fireChannelRead(msg) 把消息传给下一个——不调用就截断链。Spring Cloud Gateway / Spring WebFlux WebFilter 用的是同样思路。

# 7.4 OkHttp Interceptor

OkHttp 的 Interceptor 是工程上最优雅的责任链实现——链本身作为参数传递:

public interface Interceptor {
    Response intercept(Chain chain) throws IOException;
    interface Chain {
        Request request();
        Response proceed(Request req) throws IOException;     // ★ 触发下一个
    }
}

// 用法
OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new LoggingInterceptor())
    .addInterceptor(new RetryInterceptor())
    .addInterceptor(new AuthInterceptor())
    .build();

class LoggingInterceptor implements Interceptor {
    public Response intercept(Chain chain) throws IOException {
        Request req = chain.request();
        log.info("→ " + req);
        Response resp = chain.proceed(req);                   // ★ 调下游
        log.info("← " + resp);
        return resp;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

回到现象 ⑦——业务方说的"配 OkHttp 那样的审批链"就是这个。我们可以把审批引擎改造成一模一样的形态:

public interface ApprovalInterceptor {
    void intercept(ApprovalChain chain);
}
public interface ApprovalChain {
    Order order();
    User  user();
    void  proceed();
}

// 一条可配置的审批链
List<ApprovalInterceptor> interceptors = List.of(
    new RiskCheckInterceptor(),       // 1. 风控
    new AmountAndRoleInterceptor(),   // 2. 金额+角色匹配
    new MultiSignInterceptor(),       // 3. 大额双签
    new AuditLogInterceptor());       // 4. 审计日志
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 7.5 链的中断与短路

中断方式 链表式 递归式
不调 next 方法 直接 return 即可 不调 chain.proceed
异常中断 抛出即中断后续 抛出冒泡到上游
显式标记 设置 stopFlag 不调 proceed 即可

坑:递归式责任链如果忘记调 chain.proceed,下游永远不会执行——而且通常没有任何报错,调试极其痛苦。约定大于配置:所有 Interceptor 必须在 finally 或 try 末尾显式调用 proceed,除非业务明确要中断。

# 8. 命令与状态

# 8.1 Runnable即命令

命令模式公式:把"调用"封装成对象。

Invoker → Command → Receiver
   只知道命令      具体实现        实际操作者
1
2

JDK 中最朴素的命令:Runnable。

public interface Runnable { void run(); }

// 调用方(线程池)只知道"我有 Runnable,调它的 run 即可"
// 不关心 run 里面具体做什么
ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> processOrder(order));     // ★ Lambda 就是命令
pool.submit(new RetryableTask(...));        // ★ 显式类也是命令
1
2
3
4
5
6
7

Callable / FutureTask 是带返回值版本的命令:

Callable<Integer> task = () -> compute();
Future<Integer> f = pool.submit(task);
Integer result = f.get();
1
2
3

# 8.2 撤销与命令日志

命令模式的高阶玩法:给命令加 undo() 方法,实现撤销:

interface Command {
    void execute();
    void undo();
}

class TransferCommand implements Command {
    private final Account from, to;
    private final long amount;
    public void execute() { from.debit(amount); to.credit(amount); }
    public void undo()    { to.debit(amount);   from.credit(amount); }
}

// 命令日志(可重放)
class CommandHistory {
    private final Deque<Command> history = new ArrayDeque<>();
    public void execute(Command c) { c.execute(); history.push(c); }
    public void undoLast() { Command c = history.pop(); c.undo(); }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

数据库的 redo log / WAL 就是命令日志的工业落地——把"修改操作"作为一等公民对象保存下来,崩溃后重放。

# 8.3 状态机消除if地狱

回到现象 ④——changeStatus 12 × 8 个分支。状态模式公式:

1. 把每个状态做成独立对象
2. 状态对象自己决定能转到哪些下一个状态
3. Context 持有当前状态对象,把行为委托给它
1
2
3

重构:

public interface OrderState {
    void submit(OrderContext ctx);
    void approve(OrderContext ctx);
    void cancel(OrderContext ctx);
    String name();
}

class DraftState implements OrderState {
    public void submit(OrderContext ctx)  { ctx.setState(new SubmittedState()); }
    public void approve(OrderContext ctx) { throw new IllegalStateException("草稿不能直接审批"); }
    public void cancel(OrderContext ctx)  { ctx.setState(new CancelledState()); }
    public String name() { return "DRAFT"; }
}

class SubmittedState implements OrderState {
    public void submit(OrderContext ctx)  { /* 已提交,幂等忽略 */ }
    public void approve(OrderContext ctx) { ctx.setState(new ApprovedState()); }
    public void cancel(OrderContext ctx)  { ctx.setState(new CancelledState()); }
    public String name() { return "SUBMITTED"; }
}

class ApprovedState implements OrderState {
    public void submit(OrderContext ctx)  { throw new IllegalStateException("已审批不能再提交"); }
    public void approve(OrderContext ctx) { /* 幂等 */ }
    public void cancel(OrderContext ctx)  { 
        // 业务规则:已审批的取消需要补偿
        ctx.compensate();
        ctx.setState(new CancelledState()); 
    }
    public String name() { return "APPROVED"; }
}

// Context
class OrderContext {
    private OrderState state = new DraftState();
    public void setState(OrderState s) { this.state = s; }
    public void submit() { state.submit(this); }
    public void approve() { state.approve(this); }
    public void cancel() { state.cancel(this); }
}
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
stateDiagram-v2
    [*] --> Draft
    Draft --> Submitted: submit
    Draft --> Cancelled: cancel
    Submitted --> Approved: approve
    Submitted --> Cancelled: cancel
    Approved --> Cancelled: cancel + compensate
    Cancelled --> [*]
1
2
3
4
5
6
7
8

对比:

维度 if-else 状态判断 状态模式
加新状态 改一坨 if 加一个新类
加新动作 改所有状态分支 在所有状态类加方法
非法转换 容易漏 抛 IllegalStateException 显式拦截
可读性 一团乱麻 每个状态自治

结论:状态超过 5 个或转换规则复杂时一律用状态模式——可借助 Spring StateMachine、Squirrel-foundation 等成熟框架。

# 8.4 Thread.State状态实例

JDK 的 Thread.State 是状态枚举(不是状态模式的完整落地,但是状态机思想的体现):

public enum State {
    NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED;
}
1
2
3
stateDiagram-v2
    [*] --> NEW
    NEW --> RUNNABLE: start()
    RUNNABLE --> BLOCKED: 等 monitor
    BLOCKED --> RUNNABLE: 拿到 monitor
    RUNNABLE --> WAITING: wait/join/park
    WAITING --> RUNNABLE: notify/unpark
    RUNNABLE --> TIMED_WAITING: sleep/wait(t)
    TIMED_WAITING --> RUNNABLE: 超时/notify
    RUNNABLE --> TERMINATED: run() 结束
1
2
3
4
5
6
7
8
9
10

详细见第 35 篇。这里要强调的是:JDK 这种用枚举 + Context 行为切换 是状态模式的轻量化变体——枚举值携带行为(参考 25 篇枚举原理),既消除了类爆炸又保留了多态。

# 9. 备忘录与解释器

# 9.1 备忘录的封装边界

备忘录模式公式:

1. Originator(发起者):能够创建 Memento 保存状态、能从 Memento 恢复
2. Memento(备忘录):保存状态的对象,对外不可读
3. Caretaker(管理者):保管 Memento 列表,但不能读其内部
1
2
3

关键不变量:Memento 的内部结构只对 Originator 可见——Caretaker 只能拿着它存、取、传,不能 peek。这是为了保护被备份对象的封装性——回扣第 48 篇的封装哲学。

public class Order {
    private String status;
    private long amount;
    private List<Attachment> attachments;
    
    // 创建备忘录
    public Memento save() {
        return new Memento(status, amount, new ArrayList<>(attachments));   // ★ 深拷贝
    }
    
    // 恢复
    public void restore(Memento m) {
        this.status = m.status;
        this.amount = m.amount;
        this.attachments = new ArrayList<>(m.attachments);
    }
    
    // ★ 内部类,外部拿到只能"持有",无法访问字段
    public static final class Memento {
        private final String status;
        private final long amount;
        private final List<Attachment> attachments;
        
        private Memento(String s, long a, List<Attachment> att) {
            this.status = s; this.amount = a; this.attachments = att;
        }
    }
}

// Caretaker
class OrderHistory {
    private final Deque<Order.Memento> stack = new ArrayDeque<>();
    public void backup(Order o) { stack.push(o.save()); }
    public void undo(Order o)   { if (!stack.isEmpty()) o.restore(stack.pop()); }
}
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

# 9.2 事务回滚的备忘录影

回到现象 ⑤——undoLastOperation 一字段一字段拷贝。备忘录模式的正确做法:

  1. 由 Order 自己提供 save() / restore()——只有它知道哪些字段需要快照(包括 attachments)
  2. Memento 是 Order 的内部类——保护字段不外泄
  3. 新增字段时只改 Order.save() 一处——不会再漏

工程实战中"备忘录"常常被以下机制替代:

替代品 工作原理 范本
数据库事务 ROLLBACK redo/undo log MySQL InnoDB
Spring @Transactional 委托数据库事务 Spring AOP
不可变对象 修改返回新对象,老对象天然是备忘录 String / Record
事件溯源(Event Sourcing) 保存所有事件,重放还原状态 Axon / EventStore

# 9.3 解释器与Pattern

解释器模式:为某个语言定义文法,并提供一个解释器。

JDK 中的范本:java.util.regex.Pattern —— 把正则表达式(一种 DSL)编译成抽象语法树(节点链)后逐节点执行:

Pattern p = Pattern.compile("\\d{3,4}-\\d{7,8}");      // ★ 编译成 AST
Matcher m = p.matcher("010-12345678");
boolean ok = m.matches();
1
2
3

Pattern.compile 内部把正则字符串解析成一棵 Node 树(CharProperty / GroupHead / Loop / Branch / ...),matches() 时从根 Node 开始依次尝试匹配。这就是经典的"解释器模式 + 组合模式"。

# 9.4 何时不用解释器

疑惑:什么时候才考虑解释器模式?

论证:

  1. 文法稳定且不复杂——比如表达式 a + b * c、简单的查询过滤 name == 'Tom' AND age > 18
  2. 需要在运行时动态求值——预编译写不死
  3. 性能不是瓶颈——解释器逐节点执行天然慢

反例:业务规则引擎(Drools、Aviator、QLExpress)虽然用了类似思想,但都已演化成"AST + 字节码生成"——不再纯解释执行。纯粹的解释器模式只在玩具级 DSL 中出现。

# 10. 综合案例串讲

# 10.1 案例真相揭晓

回到第 1 章的 5 个模块审批代码,7 个疑问现在能逐条作答:

# 疑问 答案
① if-else 长龙怎么收编? 抽出 ApprovalStrategy 接口 + 多个具体策略,加规则只加新类。结合 Map/Stream 注册避免再次 if-else。见 6.1 / 6.4。
② 监听器异常阻断、同步阻塞? Observable 已废弃。改用 Flow.Publisher + SubmissionPublisher,每个 Subscriber 独立线程 + 异常隔离 + 反压。见 4.4。
③ CME 是怎么探测的?为什么"随机"? iterator 内 expectedModCount vs 集合 modCount 对账。最后一个元素之后修改不会触发 next 进而不抛——所以表面"随机"。改用 iterator.remove() 或 removeIf。见 3.2。
④ 12×8 状态分支? 状态模式:每个状态独立类,自带"能转到哪"。借助 stateDiagram 画清楚后用 Spring StateMachine 落地。见 8.3。
⑤ 一锤子拷贝快照? 备忘录模式:Originator(Order)自己提供 save/restore,Memento 是内部类。新增字段只改 Order.save 一处。或直接用数据库事务 / 不可变对象 / 事件溯源。见 9.1 / 9.2。
⑥ 横切关注点泛滥? 模板方法(骨架不变 + 钩子定制,AbstractList / HttpServlet / AQS)适合"流程一样、步骤可变";责任链(pre-post 拦截,Filter / Netty / OkHttp)适合"加横切能力"。见 5 / 7 章。
⑦ 配 OkHttp 那样的审批链? 把审批引擎改造成 ApprovalInterceptor + ApprovalChain 二接口模型——每个 Interceptor 决定 proceed 还是中断。见 7.4。

# 10.2 一个请求的旅行

把本篇 8 个模式串成一条审批请求的完整旅程——以一笔 50 万元订单的审批为例:

flowchart TD
    A[用户提交订单] --> B[Spring DispatcherServlet]
    B --> C[Filter责任链<br/>★责任链模式]
    C --> D[Auth → 限流 → 日志 → Trace]
    D --> E[OrderController]
    E --> F[ApprovalEngine.approve<br/>★策略模式选择策略]
    F --> G[LargeAmountStrategy 命中<br/>50 万属于大额]
    G --> H[执行 doApprove]
    H --> I[OrderContext 状态切换<br/>★状态模式]
    I --> J[SubmittedState → ApprovedState]
    J --> K[发布 OrderApprovedEvent]
    K --> L[Flow.Publisher 异步广播<br/>★观察者模式 + 反压]
    L --> M[审计日志 / 库存释放 / 短信通知<br/>独立线程互不影响]
    
    H --> N[Order.save 创建 Memento<br/>★备忘录模式]
    N --> O[OrderHistory 入栈<br/>支持回滚]
    
    F --> P[Iterator 遍历审批历史<br/>★迭代器模式]
    P --> Q[Spliterator 并行处理]
    
    H --> R[Runnable 提交线程池<br/>★命令模式]
    R --> S[ForkJoinPool 异步执行]
    
    E --> T[继承 HttpServlet<br/>★模板方法]
    T --> U[doPost 钩子被覆盖]
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

一笔订单审批落地,至少触发 8 种行为型模式 + 上一篇的 8 种创建型/结构型模式协作——这就是 GoF 23 模式在现代企业级框架中的真实价值。

# 10.3 设计哲学回扣

本篇 8 种行为型模式背后藏着 4 条贯穿卷七的设计哲学:

  1. 协作的复杂度,靠"对象化协议"消化——观察者把"通知"对象化、命令把"调用"对象化、状态把"状态"对象化、备忘录把"快照"对象化。复杂度不会消失,但被封装到了一个个有名字的小对象里。
  2. 流程不动、细节可填——模板方法(骨架 final + 钩子 abstract)把"什么时候做"和"具体怎么做"分开。AQS、AbstractList、HttpServlet 都是这条哲学的极致表达。
  3. 变化用组合,不用继承——策略模式用组合替代 if-else,责任链用组合替代嵌套调用,观察者用组合替代 callback 接口。Lambda 让组合的代价降到了零。
  4. 请求是一等公民——命令模式(请求对象化)、责任链(请求流过链)、观察者(请求作为事件广播)、状态(请求触发状态转换)——把请求/事件做成对象后,整个系统就有了可监控、可重放、可审计、可分布式的能力。

这 4 条哲学和第 48 篇的"封装/SOLID/组合"、第 49 篇的"封闭变化点/延迟绑定/接口契约"一脉相承——面向对象的精髓,归根结底是把抽象做对。

# 10.4 速查表

8 个行为型模式速查:

模式 一句话 JDK 范本 适用边界
迭代器 Iterator 统一遍历协议 Iterator/Spliterator 任何聚合对象
观察者 Observer 一对多通知 Flow(Observable 已废) 事件驱动
模板方法 Template 骨架不变细节可填 AQS/AbstractList/HttpServlet 流程固定步骤可变
策略 Strategy 算法可替换 Comparator/RejectedExecutionHandler 多分支算法选择
责任链 Chain 流水线分发 Servlet Filter/Netty Pipeline 横切关注点叠加
命令 Command 请求对象化 Runnable/Callable 异步、撤销、日志
状态 State 状态驱动行为 Thread.State/StateMachine 状态多+转换复杂
备忘录 Memento 保存/恢复状态 (无直接 JDK 范本) 需要回滚的场景

模式选型决策表:

业务诉求 推荐模式 反例(不要用)
一坨 if-else 选算法 策略 状态(除非真有状态转换)
流程一样但部分步骤定制 模板方法 策略(粒度不对)
横切能力(日志/鉴权/限流)叠加 责任链 模板方法(每加一个改基类)
状态超过 5 个 + 转换规则复杂 状态 if-else(必爆炸)
一对多事件通知 观察者(Flow) 直接调 listener.update(无反压)
异步任务 / 撤销 / 重放 命令 直接函数调用
回滚到某个时点 备忘录 或 数据库事务 clone()(封装泄漏)
遍历聚合且要并行 Spliterator for-i(不能并行)

Java 8+ 行为型模式 Lambda 化对照:

模式 Lambda 前 Lambda 后
策略 implements Comparator + 类 Comparator.comparing(...)
命令 implements Runnable + 类 () -> doSth()
观察者 implements Listener + 类 evt -> handle(evt)
模板方法钩子 必须子类化 高阶函数传入 Function

# 卷七收官与全册尾声

至此,卷七 设计思想与设计模式 4 篇全部完结:

卷七篇目                              核心内容
──────────────────────────────────  ──────────────────────────────
48.面向对象的真意                    封装、继承、多态、SOLID、组合优于继承
49.JDK设计模式上                     创建型 5 模式 + 结构型 3 模式
50.JDK设计模式下                     行为型 8 模式
51.SPI机制与Java模块化               服务发现 + 模块系统(待续)
1
2
3
4
5
6

下一篇 51.SPI与模块化 是全册的最后一篇——我们将沿着第 49 篇代理模式提到的"运行时织入"线索,探讨 JDK 自带的服务发现机制 ServiceLoader、JPMS 模块系统、OSGi 与 JPMS 的差别,以及 SPI 机制如何打破双亲委派——为这趟横跨 51 篇的 Java 核心原理深度之旅画上句号。

上次更新: 2026/06/10, 11:13:41
JDK设计模式上
SPI与模块化设计

← JDK设计模式上 SPI与模块化设计→

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