跳转到内容

设计原则与模式

设计原则回答"代码之上的一层怎么组织":高内聚低耦合、依赖倒置、组合优于继承。模式是反复出现的结构的命名。本章讲 SOLID、经典模式解决的问题,以及模式的滥用。

一切设计原则的地基是两个词:

  • 内聚:一个模块内部的元素为了同一个目的在一起(高内聚 = 改一个需求只动一个模块);
  • 耦合:模块之间的依赖强度(低耦合 = 一个模块的实现变化不波及别的模块)。

高内聚低耦合不是口号,是变更成本的度量:需求的变更应该被局部化。判断设计好坏的标准只有一条——下一次需求变更要动几个文件。

原则一句话实质
SRP 单一职责一个模块只有一个变更理由内聚的模块级表述
OCP 开闭对扩展开放、对修改关闭用新增代码代替修改代码
LSP 里氏替换子类能无感替换父类子类型的契约(行为兼容)
ISP 接口隔离接口小而专,别强迫实现用不到的方法接口面向调用者的需要
DIP 依赖倒置依赖抽象(接口)而非具体实现低耦合的机制

**依赖倒置(DIP)**是低耦合的核心机制:调用方定义接口,实现方实现它——接口应面向调用者的需要,而不是实现方的分类("这个类有什么方法")。

组合优于继承:继承把父类实现细节暴露给子类(脆弱基类问题:父类改动破坏子类),组合通过委托与接口复用——面向对象的核心是动态分派而非继承(→ 编程范式),组合表达"有一个",继承只用于"是一个"的真子类型(LSP 成立的场合)。

模式是反复出现的结构的命名(GoF 23 个,常用的不到十个):

模式解决的问题
策略一族可互换的算法(排序策略、支付方式)——运行时选择
观察者一对多的通知(事件、订阅)——解耦事件源与响应者
工厂创建逻辑集中(对象构造的复杂度)
适配器接口不匹配的胶水(旧接口接新系统)
装饰器不改接口加职责(中间件、I/O 包装)

模式命名了反复出现的结构,便于沟通——"这里用观察者"一句话传达一整套设计意图。价值在词汇,不在教条。

过度设计与设计不足同样有害:

  • 为未来设计("以后可能要用"):未来通常不来,来的和猜的不一样——YAGNI(You Aren't Gonna Need It);
  • 模式堆砌:三层抽象调一个加法——抽象层数是成本(每层都要读);每次抽象都要付出阅读成本,收益必须大于它("两处重复才抽象"规则——Rule of Three);
  • 接口过早固化:抽象错了比不抽象更贵(改接口 = 改所有实现)。

判断标准仍是变更成本:这个抽象让下一次变更便宜了吗? 没有就不加。

现代语言吸收了模式的机制:

  • 一等函数替代策略模式(函数即策略);
  • 闭包/lambda 简化观察者与命令;
  • generics/接口简化工厂与适配器。

Go 的教训(有意简化:无继承、无异常)与 Rust 的 trait——语言设计者在把"好的设计"内置化。模式的形态随语言演化,问题不变:解耦、复用、变更局部化。

  • 高内聚低耦合的实质是变更成本:一次需求变更要动几个文件
  • DIP 是低耦合的机制:接口应面向调用者的需要;组合优于继承
  • 模式命名反复出现的结构,便于沟通;策略/观察者/适配器覆盖多数场景
  • 过度设计与设计不足同样有害:每次抽象都要付阅读成本
  • 判断标准:这个抽象让下一次变更便宜了吗