设计模式的价值不在于“项目里出现了多少个模式名”,而在于是否把一个变化点隔离到了正确的边界。模式是沟通设计和权衡取舍的语言,不是必须复用的类模板。
本文把站内逐篇模式笔记重新编排为选择指南;每篇历史笔记仍保留其原始代码和上下文。
先定位变化点
| 变化点 | 常用模式 | 典型问题 |
|---|---|---|
| 对象如何创建 | Factory Method、Abstract Factory、Builder、Prototype | 创建过程复杂、类型组合多、需要复用已有对象 |
| 结构如何变化 | Adapter、Bridge、Composite、Decorator、Facade、Proxy | 接口不兼容、层次爆炸、需要在不修改核心对象的情况下叠加能力 |
| 行为如何变化 | Strategy、State、Observer、Command、Chain of Responsibility、Iterator、Visitor | 规则经常切换、状态转移复杂、事件生产者与消费者耦合 |
| 流程如何固定 | Template Method | 整体步骤稳定,但局部步骤需要扩展 |
| 复杂协作如何隔离 | Mediator、Memento | 对象之间互相通知,或需要保存/恢复状态 |
五个最值得先掌握的模式
Strategy:替换算法
把可变化的算法抽成同一接口,让调用方只依赖“能力”而不是具体分支。适合压缩、排序、校验、路由等规则经常变化的场景。代价是策略数量增加后需要管理注册和选择逻辑。
State:让状态自己负责转移
如果代码里出现大量 if (state == ...),可以考虑把状态相关行为放回状态对象。关键不是消除所有条件,而是让每个状态拥有清晰的合法转移和拒绝行为。
Adapter:隔离外部变化
适配器适合连接第三方库、旧接口和领域模型。优先把“外部数据如何转换”为边界问题,避免让外部类型扩散到业务层;转换失败要有可观察的错误,而不是静默返回默认值。
Observer:解耦事件生产与处理
当一个状态变化需要通知多个关注者时,Observer 能减少直接依赖。但要明确订阅顺序、重复通知、失败处理和生命周期;否则“解耦”可能只是把复杂度藏到事件总线里。
Composite:统一树形对象和叶子对象
树、菜单、场景图和权限层级都适合用 Composite 思考。它减少客户端对层级的分支,但前提是叶子与组合节点确实拥有一致的公共协议,否则不要为了“统一”而隐藏本质差异。
不应该忽略的反模式
- 只有一个实现且没有变化点,却先搭完整工厂体系。
- 策略接口泄漏了所有实现细节,导致调用方仍然知道具体类型。
- 事件总线没有所有权、顺序和失败语义。
- 装饰器层层包装后,调试日志看不出真实调用链。
- 为模仿教材而增加间接层,却没有降低耦合或提高测试性。
可以用一个问题检验设计是否值得保留:
如果删掉这个抽象,变化会扩散到多少个模块?如果抽象长期不变,它是否真的提供了隔离?
适合 C++ 后端的设计习惯
- 用组合表达运行时变化,用模板表达编译期稳定的多态选择。
- 让接口围绕调用方需要的能力设计,而不是照搬实现类。
- 为工厂、缓存、连接池和事件处理器明确生命周期与失败回滚。
- 将日志、指标和测试替身作为依赖设计的一部分。
- 先写清晰的直接实现,再根据真实的第二实现或第三种变化抽象。
复习顺序
- 先读 设计模式总览,建立分类和意图。
- 用 Strategy、State、Observer、Adapter、Facade 五个模式做小型重构练习。
- 再读创建型和结构性模式,比较它们对对象数量、接口数量和依赖方向的影响。
- 最后学习访问者、备忘录、中介者和享元,重点讨论它们适用的规模与代价。
- 每学一个模式,都写下一个“不使用它”的版本,说明选择依据。
真正成熟的代码不是模式最多,而是模式出现的地方恰好对应真实变化,且抽象的成本有清晰的收益。