
刚入行的程序员最大的困惑不是「不会写代码」,而是「写了之后改不动」。一个需求过来,发现要动几十个文件;加个新功能,原来的逻辑莫名其妙崩了;接手别人的代码,只能祈祷别点错。这些问题背后往往缺的不是语法熟练度,而是对代码结构的理解——也就是设计模式要解决的问题。
本文是设计模式系列的开篇综述,先建立全局认知:设计模式是什么、从哪来的、分几类、什么时候该用、什么时候不该用。后续文章会对每一类模式做深入拆解,并结合 PHP 开发场景给出可落地的代码示例。
设计模式到底是什么
设计模式(Design Pattern)是对反复出现的编程问题的通用解决方案。它不是让你直接复制粘贴的代码片段,而是一套经过验证的「解题思路」。就像建筑里不会每次重新发明门窗的构造一样,软件工程里也有处理对象创建、类组合、行为协作的成熟模板。
一个设计模式通常包含四个要素:
- 模式名称:一个简短、有辨识度的名字,便于团队沟通(比如「这里用工厂模式就行」)。
- 问题描述:这个模式要解决什么场景下的痛点。
- 解决方案:描述模式的核心结构——有哪些角色、怎么协作。
- 效果与权衡:用这个模式能得到什么好处,又会带来哪些额外成本。
关键认知:设计模式不是银弹。每个模式都有适用边界,盲目套用反而会让简单问题变得啰嗦。好代码的标准永远只有一个——能用最小认知负担把业务表达清楚。模式只是帮你达到这个目标的工具之一。
从 GoF 到现代:设计模式的演进
设计模式的系统化始于 1994 年,Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著的《设计模式:可复用面向对象软件的基础》(GoF 23)定义了 23 种经典模式。这本书的影响力大到「四人帮」(Gang of Four)成了设计模式的代名词。
但随着语言演进,很多当年的「模式」已经被语言特性消化了:
- 迭代器模式(Iterator)——现在 PHP 里
foreach天然支持,不用自己写。 - 观察者模式(Observer)——事件驱动框架(如 Laravel Event)已经内建了。
- 命令模式(Command)——消息队列、Job 系统的普及使其日常化。
- 单例模式(Singleton)——现代 DI 容器天然管理对象生命周期,手动写单例反而多余。
所以学设计模式不是为了「记住 23 种」,而是理解背后的组织原则。就算某个模式的具体写法不再需要手写,它的思想——封装变化、面向接口、组合优于继承——仍然是现代软件设计的基石。
三大分类:创建型、结构型、行为型
GoF 将 23 种模式分为三类。这个分类不是按技术线分的,而是按「解决什么问题」来划的。理解分类本身就比记具体模式名字更有用,因为分类直接对应日常开发中遇到的三大类痛点。
创建型模式(Creational Patterns)——对象怎么造
把对象的创建过程从使用方抽离出去,让「谁来创建」「怎么创建」「什么时候创建」可以独立变化。
| 模式 | 一句话概括 | 典型场景 |
|---|---|---|
| 单例模式 | 全局只有一个实例 | 数据库连接、配置管理(现代已少用) |
| 工厂方法 | 子类决定创建哪个对象 | 多支付渠道、多日志驱动切换 |
| 抽象工厂 | 创建一系列相关对象 | 跨平台 UI 组件族 |
| 建造者模式 | 分步构建复杂对象 | SQL 查询构建器、表单构建器 |
| 原型模式 | 克隆已有对象 | 模板配置、复杂对象复制 |
结构型模式(Structural Patterns)——类和对象怎么搭
解决「怎么用已有的东西拼出新东西」。核心思想:通过组合把不同接口、不同职责的类协同起来。
| 模式 | 一句话概括 | 典型场景 |
|---|---|---|
| 适配器模式 | 把不兼容的接口转成可用的 | 对接第三方 API,统一内部接口 |
| 装饰器模式 | 动态给对象加功能 | 中间件链、缓存层叠加 |
| 代理模式 | 给对象加一层控制 | 懒加载、权限检查、远程调用 |
| 外观模式 | 复杂系统对外提供简单入口 | Service 层封装多个 Repository |
| 组合模式 | 树形结构的统一处理 | 菜单层级、目录树、组织架构 |
| 桥接模式 | 抽象和实现独立变化 | 多数据库驱动 + 多查询构建器 |
| 享元模式 | 共享细粒度对象减少内存 | 字符对象池、游戏粒子系统 |
行为型模式(Behavioral Patterns)——对象之间怎么协作
关注的是「谁干什么」「怎么通知」「怎么把流程串起来」。这是日常开发中打交道最多的类型。
| 模式 | 一句话概括 | 典型场景 |
|---|---|---|
| 策略模式 | 算法族可互换 | 多支付方式、多排序算法 |
| 观察者模式 | 一对多事件通知 | 事件系统、消息推送 |
| 责任链模式 | 请求沿链传递直到有人处理 | 审批流、中间件管道 |
| 模板方法 | 父类定骨架,子类填细节 | 数据导入流程、报表生成 |
| 命令模式 | 把请求打包成对象 | 任务队列、撤销操作 |
| 状态模式 | 对象行为随状态改变 | 订单状态机、审批状态流转 |
| 迭代器模式 | 统一遍历方式 | 集合遍历(语言已内建) |
| 中介者模式 | 集中协调多对象通信 | 聊天室、Event Bus |
| 备忘录模式 | 快照与恢复 | 编辑器撤销、游戏存档 |
| 解释器模式 | 自定义语法解析 | 规则引擎、DSL |
| 访问者模式 | 数据结构与操作分离 | AST 遍历、报表统计 |
SOLID 原则:设计模式的底层逻辑
谈设计模式绕不开 SOLID 五大原则。你可以把 SOLID 看作「为什么要这么设计」的理论基础,设计模式则是「具体怎么做」的实践方案。
- S — 单一职责(SRP):一个类只干一件事。违反的标志是「这个类经常因为不同原因被改」。
- O — 开闭原则(OCP):对扩展开放,对修改关闭。加新功能应该加新代码,而不是改旧代码。
- L — 里氏替换(LSP):子类必须能无缝替换父类。打破这条的常见信号是子类重写方法时抛了
UnsupportedOperationException。 - I — 接口隔离(ISP):接口要小而精,不该强迫调用方依赖它不需要的方法。「胖接口」是代码耦合的温床。
- D — 依赖倒置(DIP):高层模块不应依赖低层模块,两者都依赖抽象。这就是为什么我们要写 interface、依赖注入。
把这五条放一起看,本质就一句话:让代码的修改范围尽可能小,扩展成本尽可能低。这和设计模式的目标完全一致。
什么时候该用——以及更重要的,什么时候不该用
该用设计模式的信号
- 同样的
if-else或switch在多个地方重复出现,而且每加一种新情况就要改所有地方。(考虑策略模式) - 两个类的逻辑几乎一样,只是创建对象或调用步骤稍有不同。(考虑工厂方法、模板方法)
- 你要对接一个新的第三方服务,但它的接口和现有代码风格不兼容。(考虑适配器)
- 一段逻辑需要在运行时组合多种行为,且组合方式会频繁变化。(考虑装饰器、组合)
- 修改一个类就需要连带改好几个其他类。(考虑依赖倒置、中介者)
不该用的常见陷阱
- 模式崇拜:为了用模式而用模式。一个
if能解决的问题偏要套上策略 + 工厂 + 抽象工厂,代码量翻三倍。 - 过早抽象:系统还没跑通就先画一大套 UML。先实现,等「第二遍写同样的东西」时再抽象。
- 忽略语言特性:PHP 8 有枚举、match 表达式、命名参数、Fibers,很多以前要用模式绕的问题现在语言本身就解决了。写 PHP 就用 PHP 的方式。
- 忽视性能:有些模式(如装饰器嵌套太多、观察者泛滥)会在热点路径上增加大量对象和方法调用。不一定是问题,但要做的时候心里有数。
PHP 场景下的设计模式落地
本系列文章会以 PHP 为主要示例语言(思想通用,用什么语言都能看懂),结合以下真实业务场景来讲解每个模式:
- 支付系统:多支付渠道的切换 → 策略模式 + 工厂方法
- 优惠券计算:满减、折扣、赠品的叠加规则 → 装饰器 + 责任链
- 订单状态机:待支付 → 已支付 → 已发货 → 已完成 → 已取消 → 退款中 → 已退款 → 状态模式
- 数据导出:Excel / CSV / PDF 多格式支持 → 策略模式 + 模板方法
- 消息通知:短信、邮件、站内信的多通道发送 → 观察者 + 适配器
- 多数据源切换:读写分离、分库路由 → 代理模式
每个场景都会给出「不好的写法」和「重构后的写法」的对比,让你直观感受模式带来的变化。
系列文章计划
本系列将按照「先建立分类认知,再逐个深入」的节奏展开:
- 【总述】设计模式全景指南(本文)
- 创建型模式:工厂方法与抽象工厂
- 创建型模式:建造者与原型的实战
- 结构型模式:适配器、装饰器与代理
- 结构型模式:外观、组合与桥接
- 行为型模式:策略与模板方法
- 行为型模式:观察者、责任链与命令
- 行为型模式:状态、中介者与其他
- 实战篇:用设计模式重构一个遗留业务模块
写在最后
设计模式不是面试题库,也不该是简历上凑数的那一行。它真正值钱的地方在于:当你面对一个「改不动」的系统时,你知道问题出在哪;当你开始一个新模块时,你知道过去的人踩过的哪些坑可以用哪个结构来规避。
记住三条底线:
- 模式是手段,不是目的。如果去掉模式代码更清晰,那就去掉。
- 三遍原则。第一遍正常写,第二遍发现和别处很像时重构,第三遍遇到同样的模式再考虑抽象。
- 团队共识比模式本身重要。大家不理解的「优雅模式」不如大家都能维护的「朴实代码」。
接下来的文章,我们逐个拆解每类模式,用真实 PHP 代码讲清楚每个模式的结构、适用场景和常见误区。欢迎持续关注。