设计模式概述
设计模式是指在软件设计中,对反复出现的问题提出的通用、可复用的解决方案。它不是具体的代码片段,而是一种经过验证的设计模板或思路,可以根据具体场景进行调整和实现。设计模式是软件开发中常见问题的典型解决方案,目的是让代码更易维护、易扩展、易理解、可复用。
23 种经典设计模式全览
根据 GoF(Gang of Four)的经典分类,设计模式分为创建型、结构型和行为型三大类。
创建型模式(5 种)
| 模式名称 | 核心思想 | 典型应用场景 | 现代替代方案 |
|---|---|---|---|
| 单例 | 保证一个类仅有一个实例 | 配置管理器、日志记录器、数据库连接池 | 依赖注入容器、模块化系统 |
| 工厂方法 | 定义创建对象的接口,由子类决定实例化 | 框架扩展点、插件系统 | 依赖注入、函数式工厂 |
| 抽象工厂 | 创建产品族,无需指定具体类 | 跨平台 UI 组件、数据库驱动 | 构建器模式、依赖注入 |
| 建造者 | 分步骤构建复杂对象 | 配置对象构建、DSL 链式调用 | 数据类、命名参数 |
| 原型 | 通过复制现有对象创建新对象 | 对象池、减少初始化开销 | 深拷贝函数、不可变数据结构 |
结构型模式(7 种)
| 模式名称 | 核心思想 | 典型应用场景 | 现代替代方案 |
|---|---|---|---|
| 适配器 | 转换不兼容接口 | 新旧系统对接、第三方库集成 | 函数包装器、中间件 |
| 桥接 | 分离抽象与实现 | 跨平台渲染、形状与颜色组合 | 组合优于继承、函数组合 |
| 组合 | 树形结构表示部分-整体层次 | 文件系统、UI 组件树 | 递归数据结构、代数数据类型 |
| 装饰器 | 动态添加功能 | 中间件系统、流处理 | 高阶函数、函数组合 |
| 外观 | 为复杂子系统提供简单接口 | API 网关、库封装 | 模块化导出、Facade 函数 |
| 享元 | 共享对象减少内存 | 对象池、字符串驻留 | 不可变数据、结构共享 |
| 代理 | 控制对象访问 | 远程代理、懒加载、访问控制 | 函数包装、惰性求值 |
行为型模式(11 种)
| 模式名称 | 核心思想 | 典型应用场景 | 现代替代方案 |
|---|---|---|---|
| 观察者 | 一对多依赖,状态变化通知 | 事件驱动架构、响应式 UI | 响应式流、信号机制 |
| 策略 | 算法封装,可互换 | 支付方式、排序算法、验证规则 | 一等函数、模式匹配 |
| 命令 | 请求封装为对象 | 撤销重做、任务队列、宏命令 | 一等函数、代数数据类型 |
| 状态 | 状态改变行为 | 工作流、订单状态、游戏角色 | 状态机、模式匹配 |
| 模板方法 | 父类定义算法骨架 | 框架设计、钩子方法 | 高阶函数、组合 |
| 责任链 | 请求沿处理链传递 | 日志记录、异常处理、中间件 | 函数组合、管道模式 |
| 访问者 | 不改变类结构定义新操作 | 编译器语法树、文档生成 | 模式匹配、多方法 |
| 中介者 | 封装对象交互 | 聊天室、表单验证 | 事件总线、响应式状态 |
| 迭代器 | 统一遍历集合接口 | 集合遍历、生成器 | 惰性序列、for 推导 |
| 备忘录 | 捕获对象内部状态 | 撤销功能、快照 | 不可变数据、时间旅行调试 |
| 解释器 | 定义语言文法表示 | 配置文件解析、DSL、SQL 解析 | 解析器组合子、PEG |
设计模式精选:函数式视角
随着函数式编程思想在主流语言中的普及,许多经典设计模式的存在形式发生了本质变化。一些模式被语言特性直接吸收,一些模式在高阶函数和不可变数据面前显得多余,还有一些模式则以新的面貌继续发挥作用。
模式的本质是语言缺陷的补丁
设计模式的诞生背景是 C++ 和 Java 这类早期 OOP 语言的局限性:缺乏一等函数、缺乏模式匹配、缺乏不可变数据、缺乏类型推导。当语言本身提供了这些能力后,很多模式就不再需要显式实现。
| 经典模式 | 函数式替代方案 | 本质转变 |
|---|---|---|
| 策略模式 | 一等函数传参 | 从"对象包装函数"到"函数直接传递" |
| 命令模式 | 一等函数 | 从"命令对象"到"闭包" |
| 模板方法 | 高阶函数 | 从"继承重写"到"函数组合" |
| 装饰器 | 函数组合 | 从"包装类"到f ∘ g |
| 迭代器 | 惰性序列/for 推导 | 从"迭代器对象"到"惰性求值" |
| 责任链 | 函数管道 | 从"链式对象"到pipe(f1, f2, f3) |
| 适配器 | 函数包装器 | 从"适配器类"到"λx. f(g(x))" |
| 访问者 | 模式匹配 | 从"双重分发"到"match { case... }" |
2026 年仍然值得关注的模式
并非所有模式都消失了。以下模式在现代开发中依然重要,只是实现方式更简洁。
观察者模式/响应式:Vue 和 React 的响应式系统本质是观察者模式的自动化实现,但开发者无需手动维护订阅列表。Signals(如 Preact Solid)将这一模式进一步推向声明式。
依赖注入:本质是工厂模式、策略模式和代理模式的组合。现代 DI 容器(如 Spring、.NET Core)通过类型推导和装饰器实现了极简的使用体验。
状态机:XState 等库将状态模式推向新高度,配合 TypeScript 的类型推导,可以做到状态转换的编译期检查。
组合模式:React/Vue 的组件树是组合模式的应用,配合函数式组件和 hooks,实现方式比传统的 OOP 更加简洁。
中间件模式:Express、Koa、Redux 的中间件本质是装饰器模式和责任链模式的结合,通过函数链式调用实现。
构建器模式:数据类(data class)、命名参数、解构赋值等语言特性使得构建复杂对象不再需要繁琐的 Builder 类。
被时代淘汰的模式
以下模式在函数式编程背景下逐渐淡出:简单工厂/工厂方法/抽象工厂被依赖注入容器接管、模块化系统提供了天然的工厂机制;迭代器被 for 推导、惰性序列、数组方法取代;模板方法被高阶函数和组合取代;访问者被模式匹配和代数数据类型取代;备忘录被不可变数据结构取代,天然支持时间旅行调试。
反模式
反模式是指那些看似合理但实际上会导致负面效果的设计或实践。识别反模式比学习正面模式同样重要。
架构层面的反模式
| 反模式名称 | 表现特征 | 负面影响 | 正确做法 |
|---|---|---|---|
| 大泥球 | 缺乏清晰结构,代码纠缠在一起 | 难以维护、难以测试 | 按业务领域划分模块,建立清晰边界 |
| 面条代码 | 控制流混乱,大量 goto 或深层嵌套 | 难以理解、容易出错 | 使用结构化编程,提取函数,提前返回 |
| 黄金锤 | 用一种技术解决所有问题 | 技术选型不当,过度设计 | 根据问题特性选择合适工具 |
| 造轮子 | 重复实现已有功能 | 浪费时间、质量不如成熟方案 | 优先使用成熟库和框架 |
面向对象特有的反模式
| 反模式名称 | 表现特征 | 负面影响 | 正确做法 |
|---|---|---|---|
| 上帝对象 | 一个类承担过多职责 | 难以理解、难以测试、难以维护 | 单一职责原则,拆分职责 |
| 贫血模型 | 领域对象只有数据没有行为 | 业务逻辑散落,丢失 OOP 优势 | 充血模型,行为与数据绑定 |
| 紧耦合 | 类之间依赖关系复杂 | 改一处牵动全身 | 依赖倒置,面向接口编程 |
| 滥用继承 | 为复用代码而继承 | 层次过深,脆弱基类问题 | 组合优于继承 |
| 循环依赖 | A 依赖 B,B 依赖 A | 编译失败、初始化困难 | 重新设计模块边界,引入中间层 |
| 单例滥用 | 到处使用单例 | 隐式依赖、难以测试、全局状态 | 依赖注入,限定作用域 |
代码层面的反模式
魔法数字:直接使用未命名常量,用命名常量或枚举替代。
过长参数列表:函数参数超过 5 个,使用参数对象或选项模式。
深层嵌套:缩进层级过深,使用提前返回和提取函数。
重复代码:相同逻辑出现多次,提取函数或模板。
过早优化:在未确认性能瓶颈前优化,先测量再优化。
注释掉代码:保留注释掉的旧代码,用版本控制管理历史。
捕获所有异常:空的 catch 块或捕获 Exception,精确捕获可处理的异常。