适用读者:评审/面试前 10 分钟要过一遍的人;写代码时"想不起那个模式叫什么"的人。
前置:S18-J01–J04(判据与机制)。
覆盖旧文:A-2379(第二、三份导出 761 点)、A-2392(第二、三份导出 267 点)——合计 1,028 点;其中appendix606 点、duplicate_ref733 点中的模式部分。
扩展点:本篇不新增 E 点,交叉引用 E-S18-003/004/005/006/007。
上一篇:S18-A04 · 高可用分布式架构设计 + 从需求到架构的评审清单
下一篇:S18-X02 · DDD 术语与建模模板速查。
1. 怎么用这张表
四列的分工是固定的,别混着读:
- 意图:这个模式"想把什么变化关起来"——选型看这一列;
- 场景:真实工程里长什么样——判断"我是不是遇到它了";
- 反例:什么情况下不该用它——这一列比前两列更值钱;
- 易混淆:最容易和谁搞混,以及一句话区分。
读法建议:先看"反例"列。如果反例命中你的现状,直接跳过这个模式;只有反例不命中、场景命中时,才回去看正文的实现细节。
2. 创建型(5)
| 模式 | 意图 | 场景 | 反例(别用) | 易混淆 |
|---|---|---|---|---|
| 单例 Singleton | 保证一个 ClassLoader 内只有一个实例 | 配置快照、ID 生成器、连接池门面 | 想用它藏全局可变状态;分布式"唯一" | 与 Spring 单例 Bean(每容器一个)不同 |
| 工厂方法 Factory Method | 让子类决定实例化哪个类 | 同一接口多种实现,且实现集合会增长 | 只有两个分支且一年没变 | 简单工厂(不是 GoF 23 种)、抽象工厂(产品族) |
| 抽象工厂 Abstract Factory | 一次性替换一整套相关产品 | 多数据库方言的 DAO 全家桶、多云的存储适配 | 只有一条产品线 | 工厂方法只造一个产品 |
| 建造者 Builder | 把"构造参数"与"构造过程"分开 | 参数 >5、有必填、需校验、要不可变 | 参数 ≤4 且无校验(用 record 就够) | 与"工厂"区别:工厂选类型,建造者拼参数 |
| 原型 Prototype | 复制一个已配置好的对象 | 活动模板复制、复杂配置克隆 | 需要逐字段重置业务键(那不是复制,是新建) | 与拷贝构造器:后者更安全(编译器查漏字段) |
3. 结构型(7)
| 模式 | 意图 | 场景 | 反例(别用) | 易混淆 |
|---|---|---|---|---|
| 适配器 Adapter | 把一个接口翻译成另一个 | 接第三方 SDK、兼容遗留接口 | 两边都能改(那就统一接口) | 代理/装饰都要求接口不变 |
| 桥接 Bridge | 把两个维度的继承改成"持有" | JDBC Driver/Connection、多渲染引擎 × 多平台 | 只有一个变化维度 | 与策略:桥接是结构维度,策略是行为维度 |
| 组合 Composite | 树形结构,叶子与容器同接口 | 菜单树、组织架构、规则树 | 树只有两层(用 List 即可) | 与装饰:组合是"部分-整体",装饰是"同接口叠加" |
| 装饰 Decorator | 同接口上叠加行为,可任意组合 | I/O 流、缓存/压缩/加密管线 | 只加一种能力(直接改原类) | 与代理:装饰加能力,代理控访问 |
| 门面 Facade | 给一组子系统一个简化入口 | 支付门面(下单+扣款+发券) | 门面里出现业务分支(会变上帝类) | 与中介者:门面单向简化,中介者多方协调 |
| 享元 Flyweight | 共享不可变内在状态,省内存 | 字符样式、棋盘、大量重复值对象 | 状态可变还共享(线程安全灾难) | 与对象池:享元省内存,池化省创建 |
| 代理 Proxy | 控制对目标的访问 | 懒加载、鉴权、远程 stub、AOP | 只是想要日志(用切面更省) | 与装饰:见上;与适配器:接口不变 |
4. 行为型(11)
| 模式 | 意图 | 场景 | 反例(别用) | 易混淆 |
|---|---|---|---|---|
| 责任链 Chain of Responsibility | 让请求沿链传递,直到被处理 | 校验链、风控规则、审批流 | 规则 >10 条且每条都查库 | 与拦截器:责任链多判断,拦截器多改写 |
| 命令 Command | 把"请求"变成可存储/排队/撤销的对象 | 任务队列、事务日志、撤销栈 | 只是把 if-else 换个名字 | 与策略:命令带"执行时机",策略只带算法 |
| 解释器 Interpreter | 用类表达一套文法 | 规则引擎内部、DSL | 自造表达式语言(用 SpEL/Aviator) | 与访问者:都遍历 AST,职责不同 |
| 迭代器 Iterator | 统一遍历,不暴露内部结构 | 自定义集合 | 业务代码里手写遍历器(用 Stream) | 与组合:迭代器管"逐个访问",组合管"整体结构" |
| 中介者 Mediator | 把 N×N 通信收敛成星形 | 消息总线、UI 组件协调 | 中介者里出现业务规则 | 与门面:中介者双向,门面单向 |
| 备忘录 Memento | 保存/恢复对象状态 | 编辑器撤销、流程回滚 | 状态巨大(快照成本爆炸) | 与命令:命令负责"做什么",备忘录负责"存到哪" |
| 观察者 Observer | 一对多广播 | Spring ApplicationEvent、事件总线 |
需要顺序/事务保证(用 MQ) | 与中介者:观察者是广播,中介者是路由 |
| 状态 State | 行为随内部状态改变 | 订单状态机、连接状态机 | 状态 ≤2 且无非法迁移约束 | 与策略:状态由内部迁移,策略由外部选择 |
| 策略 Strategy | 同一动作的算法可替换 | 支付渠道、折扣计算、排序策略 | 只有 2 个分支 | 与状态:见上;与工厂:工厂造对象,策略换算法 |
| 模板方法 Template Method | 骨架固定,步骤延迟到子类 | 导入/导出、批处理、生命周期回调 | 步骤顺序本身会变 | 与策略:模板用继承,策略用组合 |
| 访问者 Visitor | 不改类的前提下加操作(双分派) | AST 遍历、报表导出多种格式 | 类型集合开放(用 sealed + switch 更好) | 与迭代器:访问者管"做什么",迭代器管"怎么走" |
5. 与 S04 / S18-J02–J04 的重复关系
本文的两份原始导出(A-2379 第二/三份、A-2392 第二/三份)里,有大量"模式定义 + 优缺点 + 代码示例"的八股段。它们的归宿是 duplicate_ref,理由如下:
| 重复内容 | 主讲位置 | 本表处理 |
|---|---|---|
| 单例的五种写法与线程安全细节 | S18-J02 §2(含反射实测) | 只留一行"场景/反例" |
| 工厂三兄弟的结构与代码 | S18-J02 §3(含注册表实测) | 只留"意图/易混淆" |
| 代理三种实现(静态/JDK/CGLIB) | S18-J03 §2 | 只留"场景/反例" |
| 装饰叠加顺序的代价 | S18-J03 §3(含计数实测) | 只留"易混淆" |
| 责任链的顺序/终止/异常 | S18-J04 §2(含输出实测) | 只留"反例" |
| 模板方法的钩子与好莱坞原则 | S18-J04 §3 | 只留"意图" |
| Java 语言层面的接口/泛型/反射 | S04(Java 语言与核心 API) | 不重复,按链接指回 |
这也是本表"不写代码"的原因:代码已经在正文,速查表再抄一遍就是二次真相——同一段代码存在两处,早晚会有一处过时。
5.5 症状反查表(不知道用什么时,从症状找)
| 你现在的症状 | 候选模式 | 先排除 |
|---|---|---|
一个方法里 8 个 if (type),每次加类型都要改 |
策略 / 注册表工厂 | 分支 ≤3 → 留着 |
| 加功能要改老代码,改完还要回归全量 | 装饰 / 策略 | 只是加日志 → 切面 |
每个实体都带一堆 setXxx,状态组合非法 |
状态 / 充血实体 | 状态 ≤2 → 枚举 + 校验 |
| 一个操作要在 6 个地方各做一次 | 观察者 / 领域事件 | 需要顺序 → MQ |
| 调用外部接口,对方一改版就崩 | 适配器 + ACL | 对方可控 → 统一接口 |
| 同一段流程复制了 4 份,只有中间一步不同 | 模板方法 | 步骤会变 → 责任链 |
| 对象构造参数 12 个,还有必填 | 建造者 | 无校验 → record |
| 全局状态散落各处,测试互相污染 | 依赖注入(不是模式,是容器) | 别用单例掩盖 |
用法:先按"症状"找候选,再用 §1 的"反例"列排除,最后回正文看实现。三步之内还定不下来,说明变化点没找对——回到 S18-J01 §3 的五问。
5.6 面试口径:同一件事的两种说法
| 你原来的说法(八股) | 换成这个说法(有判断) |
|---|---|
| "我用策略模式解决了 if-else" | "支付渠道从 3 个涨到 7 个,每次加渠道要改同一个 switch,所以拆成注册表 + 策略,新增只加一个类" |
| "单例保证全局唯一" | "配置快照用容器单例,业务对象每次新建;分布式唯一性靠 Redis 锁,不靠单例" |
| "装饰器让代码更灵活" | "缓存层可拆可装,且必须装在指标的内层,否则统计口径会变成请求数而不是回源数" |
| "观察者模式解耦" | "用事务提交后的事件 + 消费端幂等;事务内同步发会在回滚后仍然通知,我们踩过" |
| "责任链实现审批流" | "链上每段只做判断不查库,数据一次性批量取;超过 6 段就改成规则引擎" |
一句话:面试官要听的是"变更点 + 代价 + 你怎么验证",不是模式名字。
6. 三条速查判据(贴在显示器上那种)
- 接口变了吗? 变了 → 适配器;没变 → 继续。
- 是在加能力,还是在控访问? 加能力 → 装饰;控访问 → 代理。
- 变化由谁触发? 外部选择 → 策略;内部迁移 → 状态;要排队/撤销 → 命令。
其余三种情况(流程、通知、遍历):
- 流程固定个别步骤不同 → 模板方法;流程不固定、要按序试 → 责任链。
- 一对多广播 → 观察者;多对多协调 → 中介者。
- 遍历/操作/快照 → 迭代器/访问者/备忘录,且优先看 Java 17 的语言特性是否已经够用。
6.5 查表时的四个误区
| 误区 | 真相 | 怎么避免 |
|---|---|---|
| 按"模式名"找答案,先定名字再找场景 | 顺序反了:先定变化点,再找模式 | 用 §5.5 的症状反查表,从症状进入 |
| 看到"反例"列写"别用"就以为永远不能用 | 反例是当前条件下不划算,条件变了结论就变 | 每次问"分支数/变化频率变了吗" |
| 把"易混淆"当成"二选一" | 很多场景要组合(如代理 + 装饰 + 责任链) | 先拆成"谁判断/谁控访问/谁加能力"三段 |
| 表里有 23 种,就想凑满 | 一个项目常用 8–10 种,剩下的只需读得懂 | 以"改动扩散面"衡量收益,不以覆盖模式数为目标 |
7. 自测(30 秒版)
- "给所有 DAO 方法加计时"该用哪个?为什么不是装饰?
- "新增一种导出格式"用工厂还是策略?两者的边界在哪?
- 只有两个支付渠道,你会抽象吗?说出判据。
- 为什么
Cloneable被 Effective Java 建议避免? - 观察者与 MQ 的分界线是什么?
下一篇
S18-X02 · DDD 术语与建模模板速查
模式查完了,下一篇查 DDD:40 个术语的一句话定义 + 出处小节、事件风暴便签模板、聚合清单模板、上下文映射表模板,以及三张需要登记的素材异常。
本篇属于系列《S18 · 架构方法与领域建模》。素材来源:A-2379/A-2392 的第二、三份导出(appendix 606 点 / duplicate_ref 733 点);定义与代码主讲在 S18-J02–J04,Java 语言基础指回 S04。