设计模式不是一套必须逐项应用的编码模板,而是对重复出现的软件设计问题及其解决思路的总结。它们真正有价值的地方,是为团队提供一套共同语言:当有人说“这里适合用策略模式”时,其他人能够迅速理解变化点、对象关系和预期边界。

GoF 设计模式共有 23 种,通常分为创建型、结构型和行为型三类:

分类数量主要解决的问题
创建型5对象由谁创建、如何创建,以及如何隐藏创建细节
结构型7类和对象如何组合,才能形成更灵活的结构
行为型11对象之间如何协作、分配职责和传递消息

本文面向已经具备开发经验、希望系统复习设计模式的读者。每种模式都从核心意图出发,配一个简单情景,并说明适用场景与使用边界。示例只用于帮助理解,不代表实际系统必须按相同方式实现。

🏭 创建型模式

创建型模式关注对象的创建过程。它们通常把“使用对象”和“构造对象”分开,使调用方不必依赖具体类型、复杂初始化步骤或固定的对象组合。

1. 单例模式(Singleton)

核心意图: 保证一个类只有一个实例,并提供统一的访问入口。

情景示例: 一个桌面应用需要统一管理用户配置。如果每个窗口都各自读取和保存配置,内存中的状态可能互相冲突。配置管理器采用单例后,所有窗口都访问同一份配置状态。

适用场景: 系统中确实只能存在一个协调者或共享资源入口,例如进程内配置、设备控制器或本地任务调度器。

注意事项: 单例容易演变成隐藏的全局变量,使依赖关系和测试隔离变差。它还需要处理并发初始化、生命周期和资源释放问题。能够通过依赖注入明确管理生命周期时,不必为了“全局只有一个”而主动实现单例。

2. 工厂方法模式(Factory Method)

核心意图: 定义创建对象的统一入口,把具体创建哪一种产品的决定交给子类或具体工厂。

情景示例: 一个通知平台支持邮件、短信和站内信。业务流程只要求获得一个“通知发送器”,不同工厂分别创建对应渠道的发送器。新增渠道时,主要扩展新的产品和工厂,而不是修改所有调用方。

适用场景: 调用方只依赖抽象产品,但具体类型会根据环境、配置或业务分支变化;创建逻辑也可能独立演进。

注意事项: 每增加一种产品通常也要增加相应工厂,类型数量会变多。若对象创建只是一行简单构造,且没有变化趋势,引入工厂方法可能只是增加间接层。

3. 抽象工厂模式(Abstract Factory)

核心意图: 创建一组彼此关联或需要相互兼容的对象,而不暴露它们的具体类型。

情景示例: 一个跨平台桌面应用需要在 Windows 和 macOS 上分别使用符合系统风格的按钮、菜单和对话框。Windows 工厂创建一整套 Windows 控件,macOS 工厂创建一整套 macOS 控件,从而避免混用不同平台的组件。

适用场景: 系统存在多个产品族,并且同一产品族中的对象必须配套使用,例如不同数据库驱动组件、不同主题的界面控件或不同云厂商的服务适配组件。

注意事项: 抽象工厂便于切换整个产品族,却不擅长频繁增加新的产品种类。若要在所有产品族中新增一种组件,通常需要修改抽象工厂及每个具体工厂。

4. 建造者模式(Builder)

核心意图: 将复杂对象的分步构建过程与最终表示分离,使相同过程能够产生不同结果。

情景示例: 一个报表系统需要生成日报、月报和审计报告。它们都要经历准备数据、生成摘要、添加明细和附加说明等步骤,但各步骤是否执行、采用何种内容并不相同。建造者可以逐步组装不同报表,最后一次性交付完整结果。

适用场景: 对象包含较多可选部分、构造步骤有顺序要求,或者直接使用一个包含大量参数的构造过程已经难以阅读。

注意事项: 建造者适合管理“如何逐步组成一个复杂对象”,并不等同于所有对象都要提供流式配置。若对象结构简单、参数很少,直接创建通常更清晰。

5. 原型模式(Prototype)

核心意图: 通过复制已有对象创建新对象,避免调用方依赖具体类型或重复执行昂贵的初始化过程。

情景示例: 一个流程设计器内置多种审批流程模板。用户选择模板后,系统复制出一份独立流程,再允许用户修改节点和负责人,而不会影响原始模板。

适用场景: 创建对象的成本较高、初始状态复杂,或者系统需要在运行时根据样板动态生成大量相似对象。

注意事项: 必须明确复制是浅复制还是深复制。对象内部如果包含可变集合、资源句柄或循环引用,复制后的共享关系很容易产生隐蔽问题。

🧱 结构型模式

结构型模式关注对象如何连接和组合。它们通过包装、桥接、树形结构或共享数据等方式,在不大幅修改现有类型的前提下形成新的能力。

6. 适配器模式(Adapter)

核心意图: 把一个已有对象的接口转换为调用方期望的接口,使原本不兼容的双方能够协作。

情景示例: 系统统一使用“支付、退款、查询状态”三个操作,但新接入的第三方支付平台使用完全不同的请求名称和数据格式。适配器负责转换调用和结果,使业务层仍然使用统一的支付接口。

适用场景: 接入旧系统、第三方 SDK 或外部服务时,已有接口无法修改,却需要融入当前系统的统一抽象。

注意事项: 适配器只应处理协议和接口差异,不宜逐渐承载大量业务规则。若适配逻辑越来越复杂,通常需要重新审视系统边界和统一模型是否合理。

7. 桥接模式(Bridge)

核心意图: 将两个可以独立变化的维度拆开,通过组合建立联系,避免用继承生成大量组合类型。

情景示例: 消息系统既有普通通知、紧急通知等消息类型,也有邮件、短信、应用推送等发送渠道。消息类型和渠道分别扩展,再在运行时组合,就不必为每一种“消息类型 × 发送渠道”建立单独的类。

适用场景: 一个概念同时存在两个或更多独立变化维度,而且这些维度会持续扩展,例如设备与遥控器、图形与渲染平台、业务抽象与底层实现。

注意事项: 桥接模式需要先正确识别变化维度。如果只是把一个简单类型机械拆成两层抽象,反而会让对象关系更难理解。

8. 组合模式(Composite)

核心意图: 把单个对象和对象组合组织成树形结构,并让调用方以一致方式处理叶子节点和组合节点。

情景示例: 企业权限系统中的菜单可以是一个具体功能,也可以是包含多个子菜单的菜单组。无论面对单个菜单还是整个菜单树,调用方都可以执行显示、隐藏或权限检查等统一操作。

适用场景: 业务天然具有“整体—部分”的层级关系,例如文件与目录、组织机构、菜单、图形容器或任务树。

注意事项: 统一接口虽然简化了调用,却可能让叶子节点暴露只对组合节点有意义的操作。设计时需要在接口一致性和类型安全之间取舍。

9. 装饰器模式(Decorator)

核心意图: 在不修改原对象的情况下,通过层层包装动态增加职责,并保持与原对象一致的使用方式。

情景示例: 一个文件存储服务最初只负责保存文件。某些场景需要额外添加压缩、加密和访问审计。每项能力都作为独立装饰器包装存储服务,业务可以按需要自由组合,而不必创建所有能力组合的子类。

适用场景: 多项附加能力需要按需叠加、顺序组合,继承又会导致子类数量快速增长时。

注意事项: 包装层过多会增加调试难度,装饰顺序也可能影响结果。装饰器应该增强原有职责,而不是悄悄改变对象的核心语义。

10. 外观模式(Facade)

核心意图: 为复杂子系统提供一个更简单、更稳定的统一入口。

情景示例: 提交订单需要依次检查库存、计算优惠、完成支付、创建物流单并发送通知。上层应用只调用“提交订单”入口,由外观对象协调多个子系统,不需要了解每个服务的调用顺序和细节。

适用场景: 子系统接口数量多、调用流程复杂,或者需要为不同层、不同客户端提供清晰的边界入口。

注意事项: 外观用于简化入口,不应成为包揽所有业务的“万能类”。复杂规则仍应由对应领域对象或服务负责,外观只做必要的协调。

11. 享元模式(Flyweight)

核心意图: 共享大量细粒度对象中可复用的内部状态,减少重复对象带来的内存开销。

情景示例: 地图上需要显示数十万个兴趣点。每个兴趣点的位置和名称不同,但同类地点可以共享相同的图标、颜色和绘制规则。系统只保留少量共享样式对象,把坐标等外部状态交给每个兴趣点保存。

适用场景: 系统存在数量极大的相似对象,内存占用已经成为真实问题,并且对象状态能够明确拆分为共享的内部状态和独立的外部状态。

注意事项: 状态拆分和共享管理会增加复杂度,还可能引入并发安全问题。只有经过测量确认对象数量和内存成本值得优化时,才应使用享元模式。

12. 代理模式(Proxy)

核心意图: 用一个替代对象控制对真实对象的访问,同时保持相同的使用入口。

情景示例: 文档系统中的大尺寸附件存放在远程对象存储中。页面先展示附件占位信息,只有用户真正打开附件时,代理才检查权限并下载实际内容。

适用场景: 需要延迟加载、访问控制、远程调用、缓存或生命周期管理,但不希望调用方感知真实对象的访问细节。

注意事项: 代理可能隐藏网络延迟、失败和权限判断等重要行为。对于远程代理,接口看起来像本地调用并不意味着它具有相同的性能和可靠性语义。

🤝 行为型模式

行为型模式关注对象之间如何分配职责与完成协作。它们经常把算法、请求、状态或通知关系独立出来,使系统能够在不修改大量调用方的情况下调整行为。

13. 责任链模式(Chain of Responsibility)

核心意图: 让请求沿着一组处理者依次传递,直到某个处理者处理它,或所有处理者共同完成处理。

情景示例: 一笔费用报销会根据金额依次经过直属主管、部门负责人和财务负责人。小额报销可能在主管处结束,大额报销则继续传递到更高层级。

适用场景: 多个处理者具有明确顺序,发送方不应依赖具体处理者,并且处理链需要动态配置或扩展。

注意事项: 必须定义请求无人处理、处理中断和异常时的规则。链条过长或顺序隐式变化,会使执行路径难以追踪。

14. 命令模式(Command)

核心意图: 把一次操作封装成独立对象,使请求能够被排队、记录、撤销、重试或延迟执行。

情景示例: 文档编辑器把插入文本、删除段落和修改格式都表示为命令。用户执行操作时记录命令,需要撤销时再执行相应的逆向操作。

适用场景: 请求需要脱离发起者独立存在,或者系统需要操作历史、任务队列、事务补偿、宏命令和撤销重做能力。

注意事项: 命令数量可能随业务操作快速增长。若要支持撤销,还必须保存足够的历史状态,并明确不可逆操作如何处理。

15. 解释器模式(Interpreter)

核心意图: 为一种简单语言定义语法表示,并通过组合语法规则解释表达式。

情景示例: 营销系统允许运营人员配置“会员等级为金卡并且订单金额大于 500”之类的优惠规则。系统把条件拆成可组合的表达式节点,再根据订单数据解释执行。

适用场景: 规则语法稳定、规模有限,需要频繁解析简单表达式,例如筛选条件、小型规则语言或领域查询语句。

注意事项: 随着语法扩展,解释器类的数量和解析复杂度会迅速增加。对于完整编程语言或高性能规则引擎,应考虑成熟的解析器、表达式引擎或专用工具。

16. 迭代器模式(Iterator)

核心意图: 在不暴露集合内部结构的情况下,按统一方式依次访问其中的元素。

情景示例: 项目管理系统中的任务可能按列表、树形子任务或分页远程数据保存。报表模块只关心逐个读取任务,不需要知道底层采用哪一种存储结构。

适用场景: 集合结构复杂、存在多种遍历方式,或调用方不应依赖集合内部表示时。

注意事项: 需要明确遍历期间集合发生修改时的行为。对于语言和集合库已经提供的标准迭代机制,通常直接使用现有能力即可,无需重复实现。

17. 中介者模式(Mediator)

核心意图: 用一个中介对象集中协调多个对象之间的交互,减少对象彼此直接依赖。

情景示例: 一个预订表单包含出发城市、到达城市、日期、航班列表和提交按钮。字段变化会影响其他多个控件。由表单协调器统一处理联动规则后,各控件只与协调器通信,不必互相引用。

适用场景: 多个对象之间存在密集、网状的交互关系,直接通信会产生大量耦合,而这些交互规则又适合集中管理。

注意事项: 中介者可能逐渐承担所有交互规则,最终变成难以维护的巨大对象。应按业务边界拆分中介者,并避免把对象自身职责也搬进去。

18. 备忘录模式(Memento)

核心意图: 在不暴露对象内部实现的前提下保存某一时刻的状态,并在需要时恢复。

情景示例: 流程设计器在用户每次重要修改后保存一个快照。用户发现调整错误时,可以回到先前版本,而外部历史管理器不需要了解流程对象内部如何保存节点和连接关系。

适用场景: 需要撤销、版本快照、检查点或临时回滚,并且希望保持对象封装边界时。

注意事项: 完整快照可能占用大量内存或存储空间,需要控制保存频率、数量和清理策略。包含外部资源的对象也未必能够通过简单快照完整恢复。

19. 观察者模式(Observer)

核心意图: 建立一对多的订阅关系,当主题状态变化时自动通知所有订阅者。

情景示例: 订单完成付款后,库存、积分、消息通知和数据分析模块都需要做出响应。订单只发布“已付款”事件,各模块独立订阅并处理自己的职责。

适用场景: 一个变化会触发多个相互独立的后续动作,发布者不应了解订阅者的具体实现,并且订阅关系可能动态变化。

注意事项: 大量隐式通知会让调用链和执行顺序难以追踪。还需要明确同步或异步、失败隔离、重复消费、退订和事件一致性等问题。

20. 状态模式(State)

核心意图: 把对象在不同状态下的行为分别封装,使对象在状态变化时表现得像切换了类型。

情景示例: 订单处于待支付、已支付、已发货和已取消状态时,允许执行的操作不同。每个状态对象负责判断本状态下能否支付、取消或发货,并决定下一状态。

适用场景: 对象行为明显依赖状态,状态转换规则较多,代码中已经出现大量围绕状态值的条件分支时。

注意事项: 状态模式会增加类型数量,还需要明确由上下文还是状态对象负责转换。若只有少量稳定状态和简单判断,直接条件分支可能更直观。

21. 策略模式(Strategy)

核心意图: 把一组可以相互替换的算法封装起来,让调用方在运行时选择合适的策略。

情景示例: 电商结算根据普通用户、会员、节日活动和企业客户采用不同的折扣计算方式。结算流程保持不变,只在计算优惠时选择对应策略。

适用场景: 同一目标存在多种实现方式,算法会独立变化,或者大量条件分支只是为了选择不同计算规则时。

注意事项: 调用方或策略选择器必须知道各策略的差异。策略过细会造成对象数量膨胀;如果算法几乎不会变化,提取策略未必带来收益。

22. 模板方法模式(Template Method)

核心意图: 在基类中规定算法的整体步骤,把部分可变步骤留给子类实现,同时保持流程顺序稳定。

情景示例: 数据导入都要经过读取文件、校验、转换、保存和生成报告,但 CSV、Excel 与第三方数据源的读取和转换方式不同。通用流程固定,各数据源只实现自己的差异步骤。

适用场景: 多个流程拥有稳定的执行骨架,仅少数步骤需要变化,并且这些实现之间具有自然的继承关系。

注意事项: 模板方法依赖继承,扩展点和执行顺序通常在编译期确定。钩子过多会让基类流程难以理解;若行为需要自由组合或运行时切换,策略模式往往更灵活。

23. 访问者模式(Visitor)

核心意图: 在不修改一组稳定对象结构的情况下,为其中不同类型的元素增加新的操作。

情景示例: 一个文档模型包含标题、段落、图片和表格。系统需要不断增加导出、统计、无障碍检查和内容审计等操作。每个访问者实现一种操作,并针对不同文档元素执行相应处理。

适用场景: 对象结构中的元素类型相对稳定,但针对这些元素的新操作经常增加,例如语法树、文档模型、编译器节点或设备清单。

注意事项: 访问者便于增加新操作,却不便增加新的元素类型,因为所有访问者都可能需要同步修改。它还可能要求元素暴露更多内部信息,削弱封装。

📋 23 种模式速查表

分类模式一句话记忆常见情景
创建型单例全局只保留一个受控实例配置管理、设备控制
创建型工厂方法把具体产品的创建推迟给具体工厂多渠道通知、文件解析器
创建型抽象工厂创建一整套相互兼容的产品跨平台控件、多云组件
创建型建造者分步骤组装复杂对象报表、复杂配置对象
创建型原型复制已有样板创建新对象流程模板、图形模板
结构型适配器转换接口以兼容旧对象或外部系统第三方支付、旧系统接入
结构型桥接分离两个独立变化的维度消息类型与发送渠道
结构型组合一致处理单个对象和树形组合菜单树、目录、组织机构
结构型装饰器通过包装动态叠加能力压缩、加密、审计
结构型外观为复杂子系统提供简单入口下单流程、子系统网关
结构型享元共享重复的内部状态地图图标、文本字形
结构型代理用替代对象控制真实对象访问延迟加载、权限、远程访问
行为型责任链让请求沿处理链依次传递审批流、校验管道
行为型命令将操作封装为可管理的对象撤销重做、任务队列
行为型解释器用对象结构表示并解释简单语法规则表达式、筛选条件
行为型迭代器隐藏集合结构并统一遍历列表、树、分页数据
行为型中介者集中协调对象间的复杂交互表单联动、聊天室
行为型备忘录保存并恢复对象快照编辑历史、流程版本
行为型观察者状态变化时通知多个订阅者领域事件、界面更新
行为型状态让行为随内部状态改变订单、工单生命周期
行为型策略在多种可替换算法之间选择折扣、路由、排序
行为型模板方法固定流程骨架,开放部分步骤数据导入、批处理流程
行为型访问者为稳定对象结构持续增加操作文档模型、语法树

🔍 易混模式辨析

工厂方法、抽象工厂与建造者

  • 工厂方法关注创建“某一种具体产品”,变化点是产品类型。
  • 抽象工厂关注创建“一组相互兼容的产品”,变化点是整个产品族。
  • 建造者关注“一个复杂对象如何分步骤组成”,变化点是构造过程和最终表示。

简单来说,工厂方法回答“创建哪一个”,抽象工厂回答“创建哪一套”,建造者回答“按什么步骤组装”。

适配器、装饰器、代理与外观

这四种模式都可能包裹已有对象,但目的不同:

模式是否保持原接口主要目的
适配器通常不保持转换成调用方需要的接口
装饰器保持叠加新的职责和能力
代理保持控制对真实对象的访问
外观提供更高层接口简化一组子系统的使用方式

判断时不要只看“是否包了一层”,而要看这一层究竟在转换、增强、控制还是简化。

策略、状态与模板方法

  • 策略模式由外部选择算法,多个策略通常可以相互替换。
  • 状态模式由对象当前状态决定行为,重点是状态转换和生命周期。
  • 模板方法通过继承固定整体流程,只允许子类改变部分步骤。

如果问题是“同一件事有多种做法”,优先想到策略;如果是“同一个对象在不同阶段表现不同”,优先想到状态;如果是“流程顺序固定,只有少数步骤不同”,可以考虑模板方法。

观察者与中介者

观察者解决的是一对多通知:发布者不关心谁订阅。中介者解决的是多对象协作:参与者把复杂交互交给一个协调中心。前者强调事件传播,后者强调交互关系收敛。

命令与责任链

命令模式把“要执行的操作”封装成对象,便于排队、撤销或记录;责任链模式组织“由谁来处理请求”的路径。一个系统可以把请求封装为命令,再让命令经过权限、校验和审计组成的责任链,两者并不冲突。

原型与备忘录

原型通过复制已有对象来创建一个新对象,重点是“创建”;备忘录保存对象某一时刻的状态供以后恢复,重点是“回退”。两者都可能涉及状态复制,但生命周期和使用目的不同。

🧭 如何选择设计模式

看到 23 种模式后,最容易出现的误区是先选一个模式,再寻找可以应用它的地方。更可靠的顺序是:

  1. 先确认当前是否存在真实、重复且值得解决的设计问题。
  2. 找出系统中稳定的部分与容易变化的部分。
  3. 判断问题主要属于对象创建、结构组合还是行为协作。
  4. 选择能够解决问题的最小方案,并比较它带来的额外类型、间接层和维护成本。
  5. 当变化消失或抽象不再成立时,允许移除模式,而不是永远保留历史设计。

设计模式的目标不是让代码看起来更“高级”,而是让变化发生时,修改能够落在清晰、有限且可预测的范围内。真正值得记住的也不是 23 个名称本身,而是它们分别隔离了什么变化、引入了什么代价,以及在什么情况下不应该使用。