跳到主要内容

布局 — 让修改只发生在一个地方

这一章讲四件事: 布局的唯一目标(效应局部化)和它的直接判据(变动率); 把东西收拢成模块的三层手段(封装、信息隐藏、关注点分离); 接口怎么把「会变的部分」关进胶囊(OCP);以及架构层的最后保险(可逆性)。 主走查是原书自带的例子:每年都要改的税法,两种放法,一次修改各自动几处。

1. 目标:效应局部化

第 03 章把重复收敛到了一处。但「一处」放在哪、和什么放在一起,决定了下一次修改要读多少代码。 这就是本章的主题——布局。作者把它的目标写成一条原则:效应局部化(Local consequences): 把修改带来的影响,控制在局部1

「效应」指修改的影响。效应不局部化是什么样:改一处,另一处完全不相干的地方跟着坏; 更糟的是你不知道哪里会坏——先得花时间排查影响范围,排查本身可能比修改还贵2。 效应局部化之后,要读的代码和受影响的面都缩在一个小圈子里。 作者还给了它的副产品:程序员只需理解当前阶段涉及的代码,不必一次掌握全部3

实现它的总手法是模块化——模块这个概念第 02 章已引入(数据与函数的打包单位), 布局要回答的正是:哪些东西该打包进同一个模块

效应局部化的判据(作者给的信号):
两个模块相互频繁调用
→ 说明「原本应该放在一起的要素,被分别放在了不同的模块中」[^4]
→ 该合并的合并,该新建的新建

图说:调用关系是布局错误的探测器。频繁互相调用=一条更紧的关系被切断了。

2. 三个思想:布局背后的价值观

作者借 Kent Beck 的《实现模式》立了三个思想,作为所有布局决定的价值坐标: 交流(第 02 章讲过)、简洁(第 03 章讲过)、灵活性(代码易于修改)4。 前两个已各自成章;灵活性在这一章展开,它有一个必须先立的警告:

灵活性是双刃剑。 代码易于修改是好事,但「灵活」经常被拿来当复杂设计的借口—— 多余的灵活性是无用的,为它服务的代码最终「成为一堆带来多余复杂性的无用代码」5。 作者的药方:不从「设计得灵活」出发,而从简洁出发、用测试兜着,自下而上地获得灵活性6

三个思想太抽象,落不到代码上。作者给了六个「桥梁」原则: 效应局部化(§1)、重复最少化(第 03 章)、逻辑与数据的一体化、对称性、声明式表达、变动率7。 本章展开其中四个;「对称性」与「声明式表达」相对次要,先给一句话版:

  • 对称性:相同的思路,在代码的任何地方都以相同的形式出现——有「添加」就该有「删除」, 同组函数用同款参数(调用时传入的输入值)。它的深层作用是消除重复的准备工作:先把相似的东西摆对称, 完全相同的部分才看得出来、才收得拢8;
  • 声明式表达:代码有两种说法,命令式描述解法(一步步怎么算), 声明式描述问题本身(什么算对)。声明式没有流程跳转,读起来就是一列事实9

3. 变动率:按修改时间分组

这一节回答:「什么和什么放在一起」的可操作判据——不看功能相似,看修改时间相同。

变动率(Rate of Change)原则一句话:同时修改的元素,放在同一个地方; 在不同时间点修改的元素,放在不同的地方10。 作者点破它的本质:这是把对称性原则应用到时间上——同样的东西同等对待, 「东西」换成「修改的时间点」11

为什么按时间分,不按功能分?因为布局服务的是下一次修改:一次修改动到的代码越集中, 效应越局部。作者给了一个判断框架:一个模块里混着多个「修改理由」, 它就脆弱——修改机会多、每次修改都要动它、改动还容易波及无关部分12

主走查:每年都要改的税法

原书的例子几乎是为走查准备的13:税额计算有两块逻辑—— 一般计算逻辑(算税的方法,多年不变)和各年固有逻辑(每年的免税额、税率档,每年变)。

放法一:两块逻辑混在同一个模块「税务」里
第 2 年改税法:
动手位置:同一模块 → 改「各年固有逻辑」的每一刀
都踩在「一般计算逻辑」旁边
风险:改错波及一般逻辑 → 所有年份的税全错
每年都要重新通读整个模块

放法二:分成「一般计算」与「各年参数」两个模块
第 2 年改税法:
动手位置:只动「各年参数」模块
「一般计算」模块一行不碰 → 它的正确性原地保持
验证范围:只测「各年参数」+ 一轮回归测试

图说:同一需求,放法二的修改面小一个量级。这就是效应局部化的直接收益。

数据同理。作者的第二个例子:一个「金融商品」模块里混着「通货」「金额」两组数据, 但汇率换算、增加币种这些修改只跟通货有关——于是把通货相关数据抽出来, 单独建一个「货币」模块。此后汇率类的修改全被「货币」模块吸收, 「金融商品」模块专注自己的计算,不再被殃及14

变动率的收成:单一职责

按变动率分组,自然会得到一种模块:只有一条修改理由。 这就是著名的单一职责原则(SRP,Single Responsibility Principle): 一个模块只该有一个修改理由;有多个,说明它承担了多项职责,而多职责的模块十分脆弱—— 任何一项职责变了,整个模块都得动,「模块很可能在不经意间遭到破坏」15。 两个方向打通了:想满足单一职责,别从职责清单出发,从「把这些代码按修改时间分组」动手, 分组完成,职责自然清晰。

4. 收拢:封装、信息隐藏、打包、关注点分离

这一节回答:确定了「什么跟什么一组」,怎么把组变成受控的模块。

原书把这组手法叫「软件架构基本技法」,并给了个好记的说法——空手道的「型」: 从开发历史中反复验证有效的固定招式,不依赖任何具体语言或开发方法16。 十招的第一式是抽象:在概念上「划清界限」,把一个模块和其他模块分开。 它由两个动作组成——舍象(舍去复杂对象的一部分性质,只盯住眼下要用的)与 一般化(从具体对象里抽出共同的性质,固定成通用的)17。 本节挑承重的四招。

封装(Encapsulation):把相互关联的数据和逻辑包进同一个模块; 铁律是只装相关的,无关的一个都不许混入18。收益清单19: 可读性高、修改影响锁在模块内、影响程度明确、模块像独立零件可复用、 复杂问题被切成小单位。

信息隐藏(Information Hiding):模块内有什么数据、函数怎么实现的,对外全部隐藏; 外部只能通过公开的少数函数来用这个模块20。 机制:公开的部分越少,模块内部的修改越不容易波及外部—— 这是把「效应局部化」从「改的时候」提前到「设计的时候」21。 注意封装和信息隐藏是两件事:封装是「把相关的收到一起」(分组), 信息隐藏是「把内部的挡在外面」(阻断访问);很多文档把后者算进前者, 作者提醒读文档时要看语境分辨22。这条分界还有一个经典版本,叫 Parnas 原则: 对使用者,只给「怎么用」必需的信息;对开发者,只给「怎么实现」必需的信息——两边都最小化23

打包(Packaging):模块多了以后,把模块按有意义的单位再分组(包)。 要点是一个反直觉的工序规定:包只能自下而上设计——先有模块,攒到一定数量再归类; 一开始就自上而下画包结构,画出来的只能是空想,「项目最初并没有可以构建的软件」24

关注点分离(Separation of Concerns):「关注点」指软件的一个功能或目的; 把每个关注点相关的代码集中成独立模块,互相之间的连接压到最低。 修改通常以关注点为单位发生,所以这样分完,每次修改天然落在一个小圈子里25。 代表模式是 MVC(Model-View-Controller,业务逻辑、界面显示、输入处理三者互相独立的经典拆法)。 有一类关注点特殊——横切关注点:日志(运行时留下的记录)这种「横穿所有功能」的东西, 写出来必然散布在每处。

日志之外还有同类:事务(要么全做、要么全不做的一组操作)、权限检查。专门的答案是 AOP(面向切面编程):把横切逻辑抽出来, 按规则自动「织入」各处,业务代码里不再出现它们的调用26

5. OCP:把会变的部分关进接口

这一节回答:封装是静态的收拢;当新行为真的要加进来时,怎么做到「加新的、不动旧的」。

OCP(Open-Closed Principle,开闭原则)要求代码同时满足两个属性: 对扩展开放(行为可以扩充)、对修改关闭(扩充时不改动既有代码)27

它需要一个关键零件:接口。接口的定义值得写全—— 接口是模块对外的约定:它列出这个模块「能做什么」(函数名、接收什么输入、返回什么结果), 但不透露内部怎么实现。调用方只看接口、只依赖接口28

作者给的构造29:先把两个角色分开。发起调用的一方,行话叫客户端。

被调用、提供功能的一方,行话叫服务器。要做的改动是:在客户端与服务器之间立一个「客户端接口」,由服务器来实现它。 之后新增一种服务器,只要它实现同一个接口,客户端一行不改就能换用它

诚实的边界:先挨第一发子弹

OCP 有一条容易被教条化的地方,作者自己戳破了:不能对所有代码要求 OCP。 在没有变更的地方套 OCP,只会让代码复杂冗长;而人不可能正确预估变更的内容。 他的替代方案非常务实30:

先豁出去挨第一发子弹(变更真的发生了),然后修改代码—— 但要修得让以后不会再在同一个位置挨子弹

也就是说,OCP 预估的不是「变更是什么」,而是「哪部分可能会变」。 书里的例子:图像处理软件,下次要加的是五边形还是圆形猜不到, 但「图形的种类会增加」猜得到——把「图形种类」藏到接口后面。 这个手法作者叫「流动元素的胶囊化」:猜内容不可行,猜部位可行31

实现层面,面向对象的多态(同一个接口,不同对象各自给出自己的实现)是最常用的工具; 设计模式里 Strategy、Observer、Template Method、Decorator 都是 OCP 的具体形32。 但 OCP 不专属面向对象:非面向对象语言里,链接时切换库也能做到——它约束的是结构,不是语法33。 同类思路在职责驱动设计里叫「受保护变化」:在稳定与不稳定的交界处, 用稳定的接口当防火墙、防水堤,把变更挡在外面34

6. 两对分离:策略与实现、接口与实现

这一节把第 4、5 节的思路再往上抽一层:把「变的部分」和「不变的部分」分开管理。

策略与实现的分离。策略=依赖软件前提条件的部分(业务规则、界面样式)——不稳定; 实现=不依赖前提、独立成篇的纯逻辑,也就是算法(一步步解决问题的固定步骤)——稳定。 两者混在一个模块里,策略一变,实现跟着陪葬,实现也无法被其他软件复用35。 UNIX 那边(第 08 章)有同一个原则的另一个名字「分离原则」, 书里的现成例子:UNIX 的 X Window 系统只实现「绘图机制」,界面风格全交给上层—— 机制稳定,策略多变,分开才能各自演化。

接口与实现的分离。模块=接口(定义「有什么功能、怎么用」)+实现(内部代码)。 使用者只需理解接口;实现随便改,只要接口不变,使用者无感36。 它的行为准则是一句格言:「针对接口编程,而不是针对实现编程」—— 模块之间的调用只走接口,不许绕到实现内部去直接调37

第 04 章到此可以收成一张总图:

修改要来(第 01 章的前提)
│ 按「修改时间」分组 → 变动率 → 单一职责
│ 分组收拢成模块 → 封装 + 信息隐藏
│ 模块多了再归组 → 打包(自下而上)
│ 横穿所有模块的职责 → 关注点分离(AOP)
│ 新行为要从外部加 → 接口 + OCP(先挨一发子弹)
│ 稳/变分离 → 策略与实现分离

每次修改都落在一个小圈子里 = 效应局部化

7. 可逆性:布局层的最后保险

这一节回答:如果连「哪部分会变」都猜错了呢?

布局是下注:把代码组织成某种结构,赌的是未来的修改方向。赌错怎么办? 作者给的答案是可逆性——程序中的决定应尽量保持「可以退回来重选」的性质, 禁止使用无法还原的方案38

它建立在一个冷静的判断上:不存在最终方案。 万事万物都在变,连你选的技术都可能被迫换——书里给的例子: 依赖某个框架开发到一半,因安全问题或许可证涨价被迫放弃, 「一旦发生这种情况,我们就要推翻前面所有的设计」39。 反例也给了:如果访问数据库的代码做了抽象(对外只暴露「存取」接口,不暴露具体数据库), 项目中途更换数据库系统,「更换操作就能在几个步骤之内完成」40

可逆性同样不许滥用——为一切做准备,设计会复杂到失控。作者给的平衡式: 按「风险发生的可能性 × 发生后的影响」决定哪里值得买这份保险41。 而且这项保险要趁早:软件与外部的交界处(配置、第三方组件)可逆性最该买, 而「在哪个交界设间接层」必须在架构设计阶段就定,事后补不了42

8. 作者的判断与证据

书里给了证据的:

  • 内聚/耦合的等级划分源自 1970 年代结构化设计(Myers),原书引日文工程教材转述——这是本章方法论里历史最久的部分;
  • OCP 出自 Bertrand Meyer《面向对象软件构造》,经 Robert Martin 推广,书内两处独立引用;
  • 「先挨第一发子弹」是对 XP 社区实践的转述,作者用它化解了 OCP 与 YAGNI 的表面冲突。

作者的推测与展开,要分开看的:

  • 六原则+三思想的框架(3.1)来自 Kent Beck,但「以三思想为选技术的理由」的解说是作者的;
  • 「金融商品/货币模块」的例子是作者构造的教学场景,非实测数据;
  • 「变动率分组自然达成单一职责」是作者的打通式解读,SRP 原文( Robert Martin)没有这个推导。

9. 边界与局限

  • 变动率要求你知道历史。 新项目没有「修改时间」数据,第一版布局只能先按当前理解粗分, 靠后续修改暴露真实变动率再调整——原书在「逻辑与数据一体化」里承认这一点: 「很难一开始就知道哪个逻辑应该和哪些数据放在一起」,跑起来后关联性才渐显43
  • 信息隐藏有成本。 隐藏过度=公开面太小,使用者要绕远路;「模块太多」的反面(第 06 章「坏味」)也会出现。原书没有给「公开多少合适」的定量标准。
  • OCP 的接口设计依赖对「流动元素」的判断,而判断会错;错判的接口本身就变成一种「多余的灵活性」——与第 03 章 YAGNI 的张力只能靠「先挨一发子弹」缓解,不能消除。
  • 可逆性买的是期权,不是结果。 框架深度耦合(依赖其特有机制)之后,可逆性实际上已丧失——书里的「中途换框架」例子成立的前提,是从第一天就没深度耦合,而这正是最难做到的。

10. 可带走的

  1. 布局只有一个目标:让下一次修改落在最小范围——先问「这次改动会牵动哪些文件」,再问「怎么让答案变少」;
  2. 探测器:两个模块互相频繁调用,是「该在一起的被拆开了」的报警;
  3. 分组看修改时间,不看功能像不像——「每年都改的」和「从不改的」必须分家;
  4. 单一职责的操作化:别数职责,数修改理由;
  5. 封装只装相关的;信息隐藏把内部挡住——两件事,分开自查;
  6. 包结构自下而上长出来,不画蓝图;
  7. OCP 的正确姿势:不猜变更内容,只猜「哪部分会变」,把它藏到接口后面;拿不准就先挨一发子弹;
  8. 针对接口编程:调用只走约定,不绕进实现;
  9. 给高风险的交界处买可逆性(配置、第三方组件、数据库访问层),便宜的部分不买;
  10. 灵活性的辩护词必须是具体的变更场景——说不出场景的灵活,就是多余复杂性的马甲。

11. 原文地图

主题原书章原文位置
三思想与六原则桥梁3.1 编程理论text/07-ch03-01-3-1.txt:12(搜「交流」) · text/07-ch03-01-3-1.txt:32(搜「六个原则」)
学技术要学演化史3.1 编程理论text/07-ch03-01-3-1.txt:81(搜「形态遵从于功能」)
效应局部化定义与收益3.5 效应局部化text/11-ch03-05-3-5.txt:9(搜「控制在局部」) · text/11-ch03-05-3-5.txt:19(搜「一无所知」) · text/11-ch03-05-3-5.txt:30(搜「频繁调用」)
灵活性双刃剑3.4 灵活性text/10-ch03-04-3-4.txt:26(搜「双刃剑」) · text/10-ch03-04-3-4.txt:32(搜「自下而上地获取灵活性」)
对称性=消除重复的准备3.8 对称性text/14-ch03-08-3-8.txt:24(搜「准备工作」) · text/14-ch03-08-3-8.txt:33(搜「添加」)
声明式与命令式3.9 声明式表达text/15-ch03-09-3-9.txt:10(搜「命令式编程」) · text/15-ch03-09-3-9.txt:14(搜「流程方面」)
变动率定义3.10 变动率text/16-ch03-10.txt:8(搜「变动率体现了」) · text/16-ch03-10.txt:10(搜「同一个地方」)
多理由模块脆弱3.10 变动率text/16-ch03-10.txt:6(搜「修改理由」)
税额例子3.10 变动率text/16-ch03-10.txt:31(搜「税额计算」)
货币模块例子3.10 变动率text/16-ch03-10.txt:39(搜「金融商品」)
单一职责原则3.10 变动率text/16-ch03-10.txt:6(搜「修改理由」) · text/16-ch03-10.txt:60(搜「自然而然」)
十技法=空手道的型3.11 软件架构基本技法text/17-ch03-11.txt:31(搜「空手道」) · text/17-ch03-11.txt:28(搜「型」)
抽象=舍象+一般化3.12 抽象text/18-ch03-12.txt:10(搜「舍象」) · text/18-ch03-12.txt:10(搜「一般化」)
封装定义与铁律3.13 封装text/19-ch03-13.txt:9(搜「外衣」) · text/19-ch03-13.txt:30(搜「混入」)
信息隐藏、封装区别、Parnas3.14 信息隐藏text/20-ch03-14.txt:9(搜「信息全部对外」) · text/20-ch03-14.txt:28(搜「封装与信息隐藏」) · text/20-ch03-14.txt:49(搜「Parnas」)
打包自下而上3.15 打包text/21-ch03-15.txt:28(搜「自下而上」) · text/21-ch03-15.txt:34(搜「图纸」)
关注点分离、MVC、AOP3.16 关注点分离text/22-ch03-16.txt:8(搜「关注点指」) · text/22-ch03-16.txt:14(搜「MVC」) · text/22-ch03-16.txt:43(搜「横切关注点」)
策略与实现3.18 策略和实现的分离text/24-ch03-18.txt:8(搜「策略或实现」) · text/24-ch03-18.txt:24(搜「量身打造」)
接口与实现、针对接口编程3.19 接口与实现的分离text/25-ch03-19.txt:6(搜「接口和实现」) · text/25-ch03-19.txt:29(搜「针对接口编程」)
OCP 定义2.6 OCPtext/06-ch02.txt:665(搜「对扩展开放」)
客户端接口构造2.6 OCPtext/06-ch02.txt:693(搜「客户端接口」)
先挨子弹、流动元素胶囊化2.6 OCPtext/06-ch02.txt:714(搜「第一发子弹」) · text/06-ch02.txt:721(搜「胶囊化」)
多态与设计模式实现 OCP2.6 OCPtext/06-ch02.txt:725(搜「多态」) · text/06-ch02.txt:730(搜「Strategy」)
受保护变化2.6 OCPtext/06-ch02.txt:737(搜「受保护变化」) · text/06-ch02.txt:742(搜「防火墙」)
可逆性定义、不存在最终方案4.4 可逆性text/71-ch04.txt:591(搜「可逆指」) · text/71-ch04.txt:600(搜「最终方案」)
换框架、换数据库的例子4.4 可逆性text/71-ch04.txt:603(搜「被迫放弃」) · text/71-ch04.txt:613(搜「数据库管理系统」)
可逆与简单的平衡4.4 可逆性text/71-ch04.txt:619(搜「平衡点」) · text/71-ch04.txt:624(搜「架构的设计阶段」)
逻辑与数据一体化要边跑边调3.7 逻辑与数据的一体化text/13-ch03-07-3-7.txt:23(搜「渐渐显露」)

Footnotes

  1. 出处:「3.5 效应局部化」第 9 段(text/11-ch03-05-3-5.txt:9,搜「控制在局部」)。

  2. 出处:「3.5 效应局部化」第 19 段(text/11-ch03-05-3-5.txt:19,搜「一无所知」)。

  3. 出处:「3.5 效应局部化」第 23 段(text/11-ch03-05-3-5.txt:23,搜「当前阶段」)。

  4. 出处:「3.1 编程理论」第 12 段(text/07-ch03-01-3-1.txt:12,搜「交流」)。

  5. 出处:「3.4 灵活性」第 26 段(text/10-ch03-04-3-4.txt:26,搜「双刃剑」)。

  6. 出处:「3.4 灵活性」第 32 段(text/10-ch03-04-3-4.txt:32,搜「自下而上地获取灵活性」)。

  7. 出处:「3.1 编程理论」第 32 段(text/07-ch03-01-3-1.txt:32,搜「六个原则」)。

  8. 出处:「3.8 对称性」第 24 段(text/14-ch03-08-3-8.txt:24,搜「准备工作」)与第 33 段(text/14-ch03-08-3-8.txt:33,搜「添加」)。

  9. 出处:「3.9 声明式表达」第 10 段(text/15-ch03-09-3-9.txt:10,搜「命令式编程」)与第 14 段(text/15-ch03-09-3-9.txt:14,搜「流程方面」)。

  10. 出处:「3.10 变动率」第 8 段(text/16-ch03-10.txt:8,搜「变动率体现了」)。

  11. 出处:「3.10 变动率」第 12 段(text/16-ch03-10.txt:12,搜「对时间应用」)。

  12. 出处:「3.10 变动率」第 6 段(text/16-ch03-10.txt:6,搜「修改理由」)。

  13. 出处:「3.10 变动率」第 31 段(text/16-ch03-10.txt:31,搜「税额计算」)。

  14. 出处:「3.10 变动率」第 39 段(text/16-ch03-10.txt:39,搜「金融商品」)。

  15. 出处:「单一职责原则」第 6 段(text/16-ch03-10.txt:6,搜「修改理由」)与第 53 段(text/16-ch03-10.txt:53,搜「遭到破坏」)。

  16. 出处:「3.11 软件架构基本技法」第 31 段(text/17-ch03-11.txt:31,搜「空手道」)与第 39 段(text/17-ch03-11.txt:39,搜「适用于一切」)。

  17. 出处:「3.12 抽象」第 10 段(text/18-ch03-12.txt:10,搜「舍象」)与第 25 段(text/18-ch03-12.txt:25,搜「一般化」)。原文的定义:抽象是「在概念上明确划清界限」,由「舍象」与「一般化」两个观点组合而成。

  18. 出处:「3.13 封装」第 30 段(text/19-ch03-13.txt:30,搜「混入」)。

  19. 出处:「3.13 封装」第 17 段(text/19-ch03-13.txt:17,搜「可读性提高」)。

  20. 出处:「3.14 信息隐藏」第 9 段(text/20-ch03-14.txt:9,搜「信息全部对外」)。

  21. 出处:「3.14 信息隐藏」第 20 段(text/20-ch03-14.txt:20,搜「控制到最小」)。

  22. 出处:「封装与信息隐藏的区别」第 28 段(text/20-ch03-14.txt:28,搜「封装与信息隐藏」)。

  23. 出处:「Parnas 原则」第 49 段(text/20-ch03-14.txt:49,搜「Parnas」)。

  24. 出处:「3.15 打包」第 34 段(text/21-ch03-15.txt:34,搜「图纸」)与第 28 段(text/21-ch03-15.txt:28,搜「自下而上」)。

  25. 出处:「3.16 关注点分离」第 23 段(text/22-ch03-16.txt:23,搜「修改范围」)。

  26. 出处:「面向切面编程」第 43 段(text/22-ch03-16.txt:43,搜「横切关注点」)。

  27. 出处:「2.6 OCP」第 665 段(text/06-ch02.txt:665,搜「对扩展开放」)。

  28. 出处:「3.19 接口与实现的分离」第 6 段(text/25-ch03-19.txt:6,搜「接口和实现」)与第 13 段(text/25-ch03-19.txt:13,搜「函数签名」)。

  29. 出处:「2.6 OCP」第 693 段(text/06-ch02.txt:693,搜「客户端接口」)。

  30. 出处:「OCP 的适用范围」第 714 段(text/06-ch02.txt:714,搜「第一发子弹」)。

  31. 出处:「OCP 的适用范围」第 721 段(text/06-ch02.txt:721,搜「胶囊化」)。

  32. 出处:「OCP 的实现与设计」第 725 段(text/06-ch02.txt:725,搜「多态」)与第 730 段(text/06-ch02.txt:730,搜「Strategy」)。

  33. 出处:「OCP 的实现与设计」第 728 段(text/06-ch02.txt:728,搜「不受语言的限制」)。

  34. 出处:「受保护变化」第 737 段(text/06-ch02.txt:737,搜「受保护变化」)与第 742 段(text/06-ch02.txt:742,搜「防火墙」)。

  35. 出处:「3.18 策略和实现的分离」第 24 段(text/24-ch03-18.txt:24,搜「量身打造」)。

  36. 出处:「3.19 接口与实现的分离」第 25 段(text/25-ch03-19.txt:25,搜「轻松使用」)。

  37. 出处:「3.19 接口与实现的分离」第 29 段(text/25-ch03-19.txt:29,搜「针对接口编程」)。

  38. 出处:「4.4 可逆性」第 591 段(text/71-ch04.txt:591,搜「可逆指」)。

  39. 出处:「4.4 可逆性」第 600 段(text/71-ch04.txt:600,搜「最终方案」)与第 603 段(text/71-ch04.txt:603,搜「被迫放弃」)。

  40. 出处:「4.4 可逆性」第 613 段(text/71-ch04.txt:613,搜「数据库管理系统」)。

  41. 出处:「4.4 可逆性」第 619 段(text/71-ch04.txt:619,搜「平衡点」)。

  42. 出处:「架构的可逆性」第 624 段(text/71-ch04.txt:624,搜「架构的设计阶段」)。

  43. 出处:「3.7 逻辑与数据的一体化」第 23 段(text/13-ch03-07-3-7.txt:23,搜「渐渐显露」)。