设计模式不是编程技巧的集合,是代码结构决策的词汇表。GoF 把 23 个模式分成创建型、结构型、行为型,这个分类便于记忆但不便于选用。实际使用时更有效的分类维度是:你的代码未来最可能沿哪个方向变化。
变更方向决定模式选择
代码的变化不是随机的。多数系统的变化集中在几个方向上:增加行为变体、叠加功能层、替换创建方式、调整模块交互。
每个变更方向对应一组模式。识别方向比记住模式名更重要,因为方向确定了,候选模式通常不超过两三个。
行为变体增加——同一个操作有多种不同的做法,而且做法还会继续增加。Strategy 把行为从控制流中抽出来变成可替换的对象;State 在此基础上增加了状态转移的约束。
功能动态叠加——核心能力不变,但需要在外面包不同的附加功能,而且包的方式在运行时才确定。Decorator 让每一层附加功能独立存在,可以任意组合和排序。
创建逻辑复杂化——对象的构建过程变复杂了,或者不同环境需要创建不同的产品族。Factory Method 处理单一产品的创建变体;Abstract Factory 处理产品族;Builder 处理多步骤构建。
模块交互膨胀——模块之间的直接调用越来越多,改一个接口牵动一串。Mediator 收拢调用关系;Observer 解耦事件的发送方和接收方。
抽象层级的平衡点
引入模式就是增加抽象层。每增加一层,灵活性提升但可读性下降。
判断平衡点的经验法则:抽象层数不应超过具体变体数。两种支付方式不需要三层工厂结构;三种日志格式不需要 Abstract Factory。
另一个判断维度是团队的理解成本。如果引入一个模式之后,团队里只有两个人能看懂代码,这个模式的实际价值是负的——它把风险从代码结构转移到了人员依赖上。
好的抽象让代码的读者不需要看实现就能理解意图。如果引入模式之后,理解代码反而需要跳更多层,说明抽象层级过了。
组合优于继承——一条贯穿全书的设计原则
23 个模式中,绝大多数都偏向组合而不是继承。原因不是继承不好,而是继承关系一旦建立就很难改变。
继承是编译时绑定。父类和子类之间的关系写死在代码里,运行时不能换。组合是运行时绑定。对象之间通过接口合作,换一个实现只需要换一个注入。
实际工程中,继承层级超过两层就要警惕。超过三层的继承链通常意味着有些职责应该被抽出来变成组合关系。
这条原则在 Decorator(用组合叠加功能替代继承扩展)、Strategy(用组合替换行为替代子类覆写)、Bridge(把抽象和实现分成两个独立的继承体系,通过组合连接)中反复出现。
模式之间的协作与冲突
实际项目中很少只用一个模式。常见的协作组合:
Factory + Strategy:工厂负责创建不同的策略对象,调用方不关心策略的具体类型。这组搭配让行为的选择和行为的创建都解耦了。
Observer + Mediator:Observer 处理事件通知,Mediator 收拢模块间的复杂交互。事件少的时候用 Observer 就够了;事件多且存在复杂的条件触发时,Mediator 更合适。
Decorator + Template Method:Template Method 定义算法骨架,Decorator 给骨架的输入输出包装附加功能。骨架不变,但进出的数据可以被灵活处理。
冲突的组合也存在。Strategy 和 Template Method 解决的问题有重叠——都是"同一个流程的某些步骤可替换"。Strategy 通过组合替换整个行为对象;Template Method 通过继承覆写个别步骤。同一个位置不要两个都用。