Chapter 05 · 辨析与判断

辨析与判断:把「读意图」放到真实场景里考一遍

04 兑现了策略 vs 状态的王牌辨析。前四章把 14 个模式逐个讲完,每个都问过「分离出什么、意图是什么」。这一章不再讲新模式——它把这些判断混在一起,强迫你在真实场景里选对那一个;再补上前四章刻意压后的一层:什么时候不该用模式。

本章你将建立的 schema

  • 把跨章的辨析连起来:同一个「包一层 / 换行为」的动作,意图不同就是不同的模式。
  • 一棵「先读意图、再定模式」的总决策树,把 14 个模式收进三个问题。
  • 2026 年的判断层:哪些模式被语言特性吸收、哪些已成反模式、什么时候模式本身就是过度设计。

5.0这一章是考场,不是讲堂

前四章每个模式单独看都「读得很顺」。但起点的警告说过:顺,只在分不清两个模式时才被戳破。下面六个场景,每一个都把来自不同章的模式摆在一起,逼你做选择。规则:先合上前面的章节,自己给出答案和理由,再展开对照。

每道题的正确答案都不靠「记住哪个模式长什么样」,而靠同一句话——这里会变的到底是什么?意图是什么?

5.1跨章辨析:六个场景

场景 1 · 行为型内部

一个游戏里的敌人 AI:平时巡逻,发现玩家后转入追击,血量过低转入逃跑。三种模式下的行动逻辑完全不同,而且这三种模式之间会互相切换。用策略还是状态?

展开答案(先写下你的判断)

状态。关键信号是「三种模式之间互相切换」,而且切换由 AI 当前所处的模式自己触发(追击中血量掉到阈值 → 自己转入逃跑)。04 章的判定法:可互换的对象之间会不会自切换?会 → 状态。

若误用策略,切换逻辑会被推到外部客户端,客户端被迫知道每种模式之间的全部转移条件——这正是状态模式要消除的东西。

场景 2 · 结构型 · 三个意图共用一个动作

要给一个第三方支付 SDK「包一层」。分别考虑三种需求:(a) SDK 的接口和你系统期望的不一致;(b) 想在每次调用前后加日志和重试,而且这些增强要能自由叠加;(c) 这个 SDK 初始化很贵,想推迟到真正调用时才创建连接。三种需求各自该用哪个模式?

展开答案(先写下你的判断)

同一个「包一层」的动作,三种意图 → 三个模式(03 章的 wrapper 四兄弟辨析):

(a) 接口不一致 → 适配器(意图:翻译接口,接口改变)。

(b) 叠加增强、可自由组合 → 装饰器(意图:加职责,接口相同但增强,由客户端控制叠几层)。

(c) 推迟昂贵的初始化 → 代理(意图:控制访问 / 懒加载,接口完全相同,代理自己管理真实对象的生命周期)。

读法永远是先问「接口变不变」,再问「意图是控访问还是加功能」。结构帮不上忙——三者都是「持有被包对象 + 委托」。

场景 3 · 创建型 · 一族 vs 分步

做一个跨平台 UI 库。需求 A:同一界面里的按钮、菜单、滚动条必须同属 macOS 风格或 Windows 风格,绝不能混搭。需求 B:要构造一个有十几个可选项(标题、图标、按钮组、回调…)的对话框,配置过程分好几步。A、B 各用哪个创建型模式?

展开答案(先写下你的判断)

A → 抽象工厂(02 章):意图是创建「一族相关产品」并保证它们同族。一致性之所以有保证,是因为整族控件都来自同一个工厂实例,混搭在结构上就不可能。

B → 建造者:意图是把「分步构造一个复杂对象」与它的表示分离,专治十几个可选参数的望远镜构造函数。

区分轴:A 关心的是「一族对象的一致性」,B 关心的是「单个对象的构造过程」。

场景 4 · 桥接 vs 策略(跨 01 / 03)

一个通知系统有两条会各自独立增长的维度:消息级别(普通 / 加急 / 紧急,各自有不同的重试与降级逻辑)和发送渠道(短信 / 邮件 / 推送)。该用桥接还是策略?

展开答案(先写下你的判断)

桥接。这里有两条正交、各自独立演化的维度(级别 × 渠道)。桥接把它们拆成两个继承体系做组合,类数量是「级别数 + 渠道数」而非「级别数 × 渠道数」(03 章)。

策略只隔离一条算法轴。若硬用策略,要么把「级别×渠道」的组合塞进一个策略族里爆炸,要么只解决一半。

回到 01 章:桥接和策略同结构,区别全在意图——桥接是结构性的(两维独立),策略是行为性的(一个算法)。又一组双胞胎,又是靠意图分开。

场景 5 · 观察者 vs 中介者

一个表单页面:多个控件之间要联动——改了「国家」下拉框要刷新「城市」列表,勾了「同上」要把收货地址复制到账单地址,等等。控件之间的联动关系很多。用观察者还是中介者?

展开答案(先写下你的判断)

倾向中介者(04 章)。这里是「多个组件之间多对多的复杂联动」,中介者把所有联动逻辑收进一个中心对象,让控件之间互不直接认识。

观察者解决的是「一个 subject 状态变了,一对多地广播给一群订阅者」——单向、一对多。若这里只是「一个数据源变了通知多个视图」,那才是观察者。

判别:一对多的单向广播 → 观察者;多对多、要消除组件间的相互引用 → 中介者。

场景 6 · 该不该用模式

Code review 时看到一段代码:定义了 interface SortStrategy,只有一个实现 QuickSort,而且排序方式在可预见的将来不会再变。作者说「这样符合策略模式,更专业」。该保留还是该改?

展开答案(先写下你的判断)

该改回内联。策略的前提是真的存在多种可互换算法。只有一个实现、且不会增加,接口只是凭空多一层间接——读代码的人要多跳一次才看到真正发生了什么,却换不来任何灵活性。这是典型的过度设计。

正确节奏:先把排序直接写着,等第二种排序需求真的出现,再重构成策略。模式是对已出现或高度可预期变化的回应,不是预先铺的脚手架。5.2 详谈这层判断。

先认出:这里会变的是什么? 对象怎么造 → 创建型 单个 → 工厂方法 一族 → 抽象工厂 分步 → 建造者 唯一 → 单例(慎用) 在外面包一层 → 结构型 接口变 → 适配器 / 外观 不变+控访问 → 代理 不变+加职责 → 装饰器 两维独立 → 桥接 内部行为 / 状态 / 通知 → 行为型 换算法,外部选 → 策略 随状态变,自切换 → 状态 一对多广播 → 观察者 定骨架填步骤 → 模板方法 遇到「双胞胎」别看类图 接口变不变 · 谁控制 · 谁触发切换 —— 全是意图信号
图 5.1一棵总决策树:14 个模式收进「会变的是什么」这一个根问题。注意:每一层的分叉条件(单个/一族、接口变不变、谁触发切换)没有一个是「类图长什么样」——全是意图信号。这就是整份教程压成的一张图。

5.22026 年的判断层:什么时候不该用模式

前四章教的是「这个模式怎么回事」。这一节补上同样重要的另一半——这个模式今天还值不值得用。GoF 的 23 个模式定稿于 1994 年,模式本身没变;变的是工程界对它们的判断。截至 2026,有三件事必须知道。

① 过度设计:模式用在不存在的变化上

场景 6 的「单实现策略」是一类。同类的还有:为每个普通对象都套一个工厂、为只有一个家族的产品预先搭抽象工厂、为不会再加步骤的对象引入建造者。它们的共同失败模式是:为了「显得专业」而引入间接,但被隔离的那处变化根本不存在。

判断口诀

模式是对已出现或高度可预期的变化的回应,不是预先铺设的脚手架。看到一个接口只有一个实现、且没有第二个的迹象,默认它是多余的间接,而不是「好设计」。先内联,等变化真的来了再重构。

② 语言特性吸收了一批模式

GoF 成书时,主流语言(C++、早期 Java)缺少一些今天习以为常的特性。当年要靠一个模式(一堆类)才能表达的东西,现在一行语言特性就够了。Peter Norvig 1996 年就指出:23 个模式里有 16 个在动态语言里「隐形或更简单」。这不是新观点,而是已成立近三十年的判断。

表 5.1 · 被现代语言特性吸收的模式(截至 2026)
语言特性吸收了哪个模式今天的写法
一等函数 / lambda策略、命令、模板方法的钩子传一个函数,而不是定义一个接口 + 一族类
语言内置迭代器(for-of / 生成器)迭代器直接用语言的迭代协议,不手写迭代器类
依赖注入(DI)容器工厂家族、单例容器管理对象创建与生命周期,不手写工厂 / 单例
sealed 类 / enum + 模式匹配状态(简单状态机)用代数数据类型 + match 表达状态,无需一族状态类

「被吸收」不等于「这个模式错了」。模式背后的意图(可互换的算法、统一的遍历、受控的创建)依然成立,只是落地的代码形态从「一堆类」变成了「一个语言特性」。读意图的能力在这里照样管用——lambda 之所以能替代策略,正因为它实现了策略的意图。

③ 一个模式已被取代:单例

单例是整个目录里判断翻转得最彻底的一个。它一度是「最好教的模式」(02 章有完整剖析)。截至 2026,它被广泛当作反模式:私有构造 + 静态访问这套机制本身,就是隐藏的全局状态,破坏单元测试隔离、把耦合藏进了看不见的地方。现代做法是把「全局唯一」交给依赖注入容器去管,而不是写一个 Singleton 类。

但 GoF 没有过时。

没有任何一个可信的声音说「设计模式已经没用了」。即使是最激烈的批评者,也保留模式作为团队的共享词汇:说一句「这里加个装饰器」,胜过三段话描述。而模式背后的原则层——松耦合、面向接口、组合优于继承——是语言无关、时间无关的。

2026 的真实共识是一个「既…又…」:原则永恒,约 16/23 的实现被语言特性吸收。它既不是「GoF 是圣经」,也不是「GoF 已死」。

稳定 已吸收 已取代 原则层:松耦合 · 面向接口 · 组合优于继承 + 模式作为共享词汇 · 适配器 / 外观 / 观察者(概念)/ 组合 被语言特性吸收(意图仍在,代码形态变了) lambda → 策略 / 命令 · 内置迭代器 · DI → 工厂 / 单例 · enum → 状态 已被取代 / 反模式 单例 → 依赖注入 · 「到处套工厂」→ cargo-cult 过度设计
图 5.2设计模式在 2026 的三态。注意:中间一层不是「错了」,是「意图还在、代码形态被语言吃掉了」——读意图的能力让你看穿这一点。只有最下面一层(以单例为代表)是真的被取代了。

§自测题库:三层梯度

上面六个场景已经是「应用判别层」。下面补概念层与原理层,凑齐三层梯度。先合上教程,把答案写下来,再展开对照。

概念层(对应 01–04 章)

  1. 用一句话说出「封装变化」是什么,以及它的两种落地机制。
  2. 装饰器和代理都「实现与被包对象相同的接口」。把它们分开的两个信号是什么?
  3. 抽象工厂和工厂方法,一个用组合、一个用继承——分别是哪个用哪个?

原理层(对应各章「底层机制」)

  1. 为什么说单例的「测试不友好」是它机制的内禀属性,而不是用错了它?
  2. 组合(Composite)被说成「明知故犯地违反接口隔离原则」,具体违反在哪里?这个权衡换来了什么?
  3. 模板方法和策略都能「替换算法」。为什么说一个在编译期固定、一个在运行期可换?

应用判别层 · 加试

  1. 一个日志库:用户可以决定日志「先脱敏、再压缩、再加时间戳」,顺序和组合任意。这是装饰器还是责任链?给出判断信号。
  2. 你看到一段代码:PaymentFactory 里一个 switch(type) 返回不同支付对象,支付类型还在不断加。这是「简单工厂」。要不要升级成工厂方法或抽象工厂?依据是什么?
亲手画一张图

合上整份教程,凭记忆在纸上重画起点的概念地图:顶部的引擎是什么、中间那条因果链怎么推到「读意图」、底下分哪三组。画完翻回 0.4 对照——你画的图里,有没有那条横跨三组的「双胞胎」带?如果漏了它,说明主线还没真正长进你的记忆里,回 01 章再走一遍 §1.4。

答案(先把三层都做完再展开)

概念层

  1. 把一处将来会改的代码隔离到稳定接口后,使其能独立替换。两种机制:面向接口编程、组合优于继承。
  2. ① 接口是否改变——都不变,但代理意图是控制访问、装饰器意图是加职责;② 谁控制组合与生命周期——装饰器由客户端叠加,代理自己管理被代理对象的生命周期。
  3. 抽象工厂用组合(一个工厂对象挂多个 create 方法),工厂方法用继承(子类重写 create)。抽象工厂内部常常正是用工厂方法实现的。

原理层

  1. 因为「私有构造 + 静态全局访问」这套机制本身就引入了全局状态:测试时无法替换 / mock 它,且实例状态跨测试用例残留。问题出在机制,不在用法,所以换个用法救不了。
  2. 违反点:add() / remove() 这种「管理子节点」的方法被放进了叶子和容器共享的接口,而叶子根本没有子节点。换来的是:客户端能统一对待叶子和容器,递归遍历整棵树时无需区分类型。
  3. 模板方法用继承:步骤由子类在编译期重写,顺序被父类锁死,运行时换不了。策略用组合:算法对象在运行时被持有,可随时替换。

应用判别层 · 加试

  1. 装饰器。信号:每一层都「实现相同接口 + 持有上一层」,可任意叠加顺序,且输出仍是同一个日志接口。责任链的意图是「让一个请求沿链传递,直到某个处理者拦下它并终止」——日志这里没有「拦下并终止」,每一层都要执行,所以是装饰器不是责任链。
  2. 看「变化是哪一类」:支付类型在单维度持续增加,且每种类型是单个产品 → 工厂方法即可(每种支付一个创建者子类,新增类型不改旧代码)。只有当出现「整族相关产品要保持一致」(如同时换一整套支付+对账+发票实现)时,才升级到抽象工厂。别为了升级而升级——若 switch 改动频率低、类型增长平缓,简单工厂留着也没错。
进阶挑战 · 刚好够不着

给一段真实代码做「模式体检」

找一个你正在维护的真实项目,挑一个超过 200 行、改动频繁的类或模块。用本教程的眼光给它做一次体检,回答三个问题:(1) 这里真正会变的是哪一处?(2) 它现在有没有被隔离到一个接口后面,还是和不变的部分焊在一起?(3) 如果要重构,该上哪个模式——还是说,它其实不需要模式,只是需要把一个函数拆开?

提示(卡住再展开)

最难、也最值钱的是第 (3) 问。新学完模式的人倾向于到处看见模式;但很多「坏代码」的正解是「拆个函数、起个好名字」,而不是「上一个模式」。判断分界:只有当那处变化会以多种形态反复出现(多种算法、多种产品、多个状态)时,才值得用模式把它隔离;一次性的复杂逻辑,拆函数就够了。能分清这两者,你就越过了「为模式而模式」这道坎。