《实现模式》——Implementation Patterns

作者:Kent Beck 译者:李剑、熊节、郭晓刚 出版:人民邮电出版社,2009年1月

核心观点

好的代码首先是给人读的,其次才是给机器执行的。

Kent Beck 在本书中将自己多年的编程习惯和阅读代码的经验,凝练成一套完整的编程理论:价值观 → 原则 → 77 种实现模式。这不是一本风格指南,也不是一本设计模式书,而是关于”如何用代码与他人沟通”的书。

任何一个傻瓜都能写出机器能懂的代码。好的程序员应该写出人能懂的代码。 —— Martin Fowler(引自本书译者序)

三层结构:价值观 → 原则 → 模式

第一层:价值观(3 个)

价值观是编程的统一支配性主题,影响每个决策,但难以直接应用。

价值观核心含义
沟通代码的首要目的是向其他开发者传达意图。程序更多的时候是被阅读,而不是被编写。
简单去掉代码中多余的复杂性。“不要过早优化,但也不要过早劣化。“
灵活保持开放的心态,代码应该易于适应未来的变化。没有”完工”一说,修改的投入远大于最初编写的投入。

第二层:原则(6 个)

原则介于价值观和模式之间——比价值观更贴近编程场景,比模式更通用。在没有现成模式可用时,可以用原则推导解决方案。

原则含义
局部化影响(Local Impact)一个变化应该只影响局部范围,避免牵一发而动全身。
最小化重复(Minimize Duplication)重复是万恶之源,每一处重复都是未来修改时的潜在陷阱。
将逻辑与数据捆绑(Bind Logic and Data)数据和操作它的逻辑应该放在一起,这是面向对象的核心思想。
对称性(Symmetry)代码在结构上应该对称——对称的代码更容易理解和预测。
声明式表达(Declarative Expression)尽可能用”是什么”而非”怎么做”来表达意图。
变化率(Rate of Change)将一起变化的东西放在一起,变化频率不同的东西分开。

第三层:实现模式(77 种)

模式是可以直接套用的具体编程实践。以下按章节分类:


第 5 章:类(Class)

类是面向对象编程的基本组织单元。本章讨论如何设计类,使其清晰表达意图。

模式核心思想
简单的超类名超类用简洁的名词命名(如 List),不需要前缀描述其抽象程度。
限定性的子类名子类用修饰词 + 超类名的方式命名(如 ArrayList),一眼看出继承关系。
抽象接口接口描述”能做什么”,而不是”是什么”。接口名应该是形容词或动词短语。
interfaceJava 中用 interface 定义契约,实现可以有多个。
抽象类当有公共实现需要复用时用抽象类,否则优先用 interface。
有版本的 interface接口演化时,发布新版本接口而非修改旧接口,保持向后兼容。
值对象不可变对象,相等性由内部值而非身份决定。大量使用值对象可以降低状态管理复杂度。
特化 vs 子类特化是”是一种”关系(用继承),子类是”复用代码”的手段——两者不要混淆。
实现器(Implements)用 implements 声明实现的接口,清晰表达契约。
内部类当一个类只在另一个类内部使用时,定义为内部类,减少命名空间污染。
实例特有的行为不同实例有不同行为时的几种实现方式(见下方)
条件语句最简单直接的方式:if/else 判断实例类型 → 但违反开闭原则。
委派将变化的部分委派给另一个对象(策略模式)。
可插拔的选择器在运行时动态选择方法(反射/函数字典)。
匿名内部类用于一次性的小回调,避免创建大量一次性命名类。
库类不要修改框架/库类,用组合或工具类扩展。

第 6 章:状态(State)

译者序中特别强调:“至少应该读完第 6 章’状态’。因为在各种常见的低级错误中最常见的就是关于’什么信息在什么地方’的决策错误。“

状态的访问方式

模式适用场景
直接访问直接读写字段。简单直接,但暴露了内部实现。
间接访问通过 getter/setter 访问。增加了一层间接,可以在访问时加入逻辑(如延迟计算、校验)。

状态的可变性

模式说明
可变状态默认选择,但需要谨慎管理状态变更。
不可变状态一旦创建就不能修改(值对象)。线程安全、易于推理,但创建新对象有开销。

变量的分类

类型生命周期作用域
局部变量方法调用期间方法内部
字段对象存活期间整个对象
参数方法调用期间方法内部,从外部传入
收集参数在多个方法间传递、累积结果的参数对象

其他状态模式

  • 可选参数:方法有多个参数时,用重载或参数对象简化调用
  • 变长参数:参数数量不确定时用 ...
  • 参数对象:当参数超过 3 个时,封装成对象传递
  • 常量:用 static final 声明的不变值
  • 按角色命名:变量名应反映其在逻辑中的角色,而非其类型
  • 声明时的类型:变量的声明类型应该尽可能抽象(面向接口编程)
  • 及早初始化:在声明时就初始化,避免 null 问题
  • 延迟初始化:第一次使用时才初始化,用于创建成本高且不一定用到的对象

第 7 章:行为(Behavior)

控制流的几种形式

模式说明
主体流方法的主流程应该清晰可见,把异常情况和错误处理推到边缘。
卫述句(Guard Clause)用提前返回处理异常情况,减少嵌套,让主流程更清晰。

消息模式

方法调用本质是对象之间传递消息。

模式说明
选择性消息根据条件选择调用不同方法(多态是其中一种形式)。
双重分发两个对象的类型都影响行为时使用(如 Visitor 模式)。
分解性消息把一个大方法拆成多个依次调用的小方法。
反置性消息把调用关系反过来——“你调用我”变成”我回调你”。
邀请性消息调用方邀请被调用方参与计算,而非命令式地获取数据。
解释性消息给复杂的逻辑块起一个好的方法名,用方法名解释代码在做什么。

异常流

模式说明
异常用异常处理错误,不要用返回值表示错误。
已检查异常对于调用方应该且能够处理的异常用 checked exception。
异常传播对于无法处理的异常,直接抛出(或包装后抛出),不要吞掉。

第 8 章:方法(Method)

方法设计原则

模式核心思想
组合方法(Composed Method)一个方法应该由几个同级抽象层的子步骤组成,每个步骤有清晰的命名。
揭示意图的名称方法名应该清楚地说明”做什么”,而不是”怎么做”。
方法可见性公开方法越少越好,能用 private 就不用 protected,能用 protected 就不用 public。
方法对象当一个方法太复杂时,把它提升为一个对象,用多个小方法和字段组织逻辑。

方法类型

分类典型模式
转换转换方法(toString()、toArray())、转换构造器
创建完整的构造器(构造后对象立即可用)、工厂方法、内部工厂
访问Getting 方法、Setting 方法、查询方法(返回 boolean,用 is/has/can 前缀)
布尔值 Setting 方法用两个命名方法替代 setState(boolean),如 enable() / disable()
相等性判断equals() 和 hashCode() 必须同时正确实现
安全副本返回可变对象的副本,防止外部修改内部状态
容器访问器方法不要直接返回内部容器引用,返回不可修改视图或副本

第 9 章:容器(Collections)

容器接口的选择

接口适用场景
Array大小固定、性能敏感时使用。
Iterable只需要遍历,不需要知道具体类型。
Collection一组元素,不关心顺序和重复。
List有序、允许重复。
Set不允许重复。
SortedSet有序的 Set。
Map键值对映射。

容器实现的选择

选择实现类时需要考虑:性能特征(读多还是写多)、是否需要排序、内存开销等。

Collections 工具类

  • 查询(binarySearch 等)
  • 排序(sort)
  • 不可修改的容器(unmodifiableList/Set/Map)——返回只读视图
  • 单元素容器(singletonList/Set/Map)
  • 空容器(emptyList/Set/Map)——比 null 更好的选择

继承容器

尽量不要继承容器类来扩展功能,优先使用组合。


第 10 章:改进框架

讨论框架演化的艺术——如何在修改框架的同时不破坏已有的应用代码。

核心困境

框架需要不断改进以适应新需求,但每个修改都可能破坏已有的使用者。

不兼容的更新

有些修改必然不兼容,需要明确通知使用者并提供迁移路径。

鼓励可兼容的变化

  • 程序库类:框架中的类应该设计得让使用者可以方便地扩展,而不需要修改框架本身
  • 对象:把扩展点设计在对象层面,通过组合/继承/回调等方式扩展

附录 A:性能度量

提供了一套性能度量的工具和方法,用来测量不同实现方式的实际性能差异。

核心观点:不要凭感觉优化,先测量。 很多”常识”中的性能结论在实际场景中并不成立。

包含对各种容器实现(ArrayList vs LinkedList、不同 Set、不同 Map)的性能比较。


关键概念提炼

1. 代码即沟通

全书最核心的思想:代码的主要读者是人,不是编译器。编写代码时应该始终想着”下一个读这段代码的人能不能快速理解我的意图”。

2. 模式是压力的解决方案

每个模式背后都有一系列”压力”(force)——可读性、性能、灵活性、简洁性之间的权衡。理解了压力,才能在合适的场景选用合适的模式。

3. 从价值观到模式的三级跳

  • 价值观告诉你方向(要沟通、要简单、要灵活)
  • 原则告诉你路径(局部化影响、消除重复……)
  • 模式告诉你具体怎么走(77 种可直接套用的实践)

当你遇到模式没覆盖的场景时,回到原则;当原则也无法判断时,回到价值观。

4. 命名是最重要的实现模式

好的命名本身就是文档。揭示意图的名称、按角色命名、简单的超类名、限定性的子类名——命名模式占据了本书很大篇幅。

5. 状态管理是低级错误的重灾区

译者序特意强调了第 6 章。“什么信息放在什么地方”——变量的作用域、生命周期、可变性——是编程中最常见的低级错误来源。


与其他知识的关联

  • 《重构》-改善既有代码的设计 —— 姊妹篇。《重构》讲如何修改已有代码,《实现模式》讲如何从一开始就写出好代码。两者共享”代码可读性”的核心价值观。
  • 《SICP》-计算机程序的构造和解释-第二版 —— 从更基础的层面讨论程序的本质。实现模式是工程层面的实践,SICP 是理论层面的奠基。
  • 《程序员修炼之道》-从小工到专家 —— 更广泛的程序员职业素养,实现模式是其中”编码”这一维度的深入展开。
  • 《代码整洁之道》 —— 主题高度重合,但本书更结构化(价值观→原则→模式三层),更偏 Java 语言层面的具体实践。
  • Kent Beck 的另一部著作《测试驱动开发》(TDD)—— 实现模式和 TDD 是同一套编程哲学的两个侧面。

适合谁读

  • 初学者:建立正确的编程审美观,从一开始养成好习惯
  • 资深工程师:把已有的直觉经验系统化,理解”为什么要这样写”
  • 技术 Leader:作为团队代码规范的理论基础,比纯规则的代码规范更有说服力

“如果你确实没有时间,至少应该读完第 6 章’状态’。” —— 译者序