先提问题:什么是设计模式?设计模式是怎么演化来的?软件开发为什么需要设计模式?
设计模式是软件工程中用来解决特定问题的通用且可重用的方案,它是软件开发过程中通过自下而上总结归纳提炼出来的智慧结晶。
要回答为什么需要设计模式,需要先回答为什么需要软件工程,软件工程作为一个学科出现,主要源于计算机科学和工业界对软件开发过程中的复杂性和规模性增长的需求。在计算机发展早期,软件开发主要是一些简单的程序,由个别专家编写和维护,这样的工作方式在很大程度上能够满足当时的需求。
但是随着计算机技术的快速发展,软件系统的规模和复杂性急剧增加,大型软件系统的开发变得日益困难,软件项目的失败率和成本超支成为普遍现象,软件开发过程中出现了很多问题:
-
成本和时间超支,需要比预期更多的金钱和时间
-
产品质量问题,由于开发过程中缺乏规范和方法论的支持,软件产品的质量难以保证
-
可维护和可扩展性问题,随着规模的增长,软件产品的维护和升级变得越来越困难
-
项目管理困难,缺乏有效的项目管理和协作方法,导致团队间沟通不畅,效率低下
要解决软件开发过程中出现的这些问题,需要以工程化的方式引入方法论和规范化的开发流程,其中设计模式就是软件开发过程中总结出来的方法论,主要解决软件质量、软件可维护可扩展性的问题,此外使用专业词语也有利于团队沟通,提升协作效率。
-
设计模式有很多,但它们都遵循一些通用的设计原则,包括但不限于:
-
单一职责原则:一个类/结构体只负责一项任务,职责尽可能单一,不要承担过多功能
-
开放/关闭原则:软件实体(类/结构体、模块、函数等)应该对扩展开放,对修改关闭,软件应该设计成容易扩展,防止乱改
-
里氏替换原则:在继承结构中,子类应该能够替换基类中的所有实例,而不引入任何新的行为(不过Rust中没有继承的概念,Rust通过组合和多态性来模拟继承的功能)
-
接口隔离原则:客户端不应该强迫依赖它们不需要的接口
-
依赖倒置原则:高层模块不应该依赖底层模块,二者都应该依赖于抽象。抽象不应依赖细节,细节应依赖抽象
一、先不要急着学“模式名”,先补 Rust 的设计基础
Rust 的设计模式不是独立存在的,它基本都建立在这些语言机制上:
struct:组织数据
impl:给数据添加行为
trait:抽象行为
enum:表达状态和分支
match:处理不同情况
泛型:编译期抽象
dyn Trait:运行时多态
Iterator:数据流处理
Option / Result:空值和错误处理
Rc / Arc / RefCell / Mutex:共享和可变性管理
所以第一阶段,你应该先问自己:
Rust 不靠继承,它靠什么做抽象? Rust 不用 null,它怎么表达“可能没有”? Rust 不用异常,它怎么处理错误? Rust 不鼓励全局可变状态,它怎么共享数据? Rust 怎么表达“状态变化”?
这些问题弄懂之后,再看设计模式会轻松很多。
二、按“问题场景”学习,而不是按模式名字学习
每个设计模式本质上都是在解决一个问题。
你可以这样学:
我想创建对象
→ 学工厂模式、构建者模式、原型模式、单例模式
我想把复杂系统封装起来
→ 学外观模式
我想让两个接口不兼容的东西能一起工作
→ 学适配器模式
我想给对象额外增加功能
→ 学装饰器模式、代理模式
我想让不同算法可以切换
→ 学策略模式
我想把一个操作封装成任务
→ 学命令模式
我想让多个对象之间通信
→ 学观察者模式、中介者模式
我想把请求一层一层传下去
→ 学责任链模式
我想表达对象状态变化
→ 学状态模式、备忘录模式
我想处理树形结构
→ 学组合模式
我想解释一种简单语言或表达式
→ 学解释器模式
我想遍历复杂结构
→ 学访问者模式、迭代器模式
这样学比死记模式定义更有效。
三、学习每个模式时固定用这 5 个问题
每学一个设计模式,都按照这 5 个问题来理解。
1. 它解决什么问题?
不要先背定义,要先想场景。
例如策略模式不是“定义一系列算法并封装起来”,而是:
我有多个算法,运行时可能要切换,不想在代码里写一堆 if else。
这样你就知道它为什么存在。
2. 在传统面向对象语言里怎么写?
比如 Java 可能用:
接口
抽象类
继承
多态
对象组合
你不需要深入写 Java 代码,但要知道传统模式原来是为了解决什么结构问题。
3. 在 Rust 里应该用什么实现?
这是最关键的。
Rust 里很多模式不是靠“类继承”,而是靠:
trait
enum
match
泛型
闭包
智能指针
所有权转移
比如:
策略模式 → trait / 泛型 / dyn Trait
状态模式 → enum / match / 类型状态
原型模式 → Clone
单例模式 → OnceLock / LazyLock
迭代器模式 → Iterator
过滤器模式 → filter
适配器模式 → From / Into / 包装结构体
模板方法模式 → trait 默认方法
你要建立这种映射关系。
4. 这个模式在 Rust 中有没有更自然的写法?
这是 Rust 学设计模式最容易忽略的一点。
有些模式在 Java 里很有必要,但在 Rust 里可能不用写得那么复杂。
例如:
访问者模式
在 Java 里常用于处理复杂对象结构,但 Rust 里很多时候用:
enum + match
就够了。
再比如:
原型模式
传统写法可能要定义 prototype 接口,但 Rust 里通常直接用:
Clone
所以你要学的是:
这个模式在 Rust 中是否需要完整实现? 还是已经被 Rust 的语言机制替代了?
5. 它适合什么项目场景?
每个模式最后都要落到实际应用上。
比如:
构建者模式:配置对象、HTTP 请求、数据库连接参数
观察者模式:事件系统、GUI、消息通知
责任链模式:Web 中间件、请求校验、日志处理
命令模式:任务队列、撤销重做、CLI 指令
状态模式:订单流程、网络连接、下载任务、编译流程
解释器模式:表达式计算、规则引擎、小型 DSL
组合模式:文件目录树、UI 组件树、语法树
代理模式:缓存代理、权限检查、日志代理
外观模式:对外暴露简单 API,隐藏内部复杂模块
只要能想到真实场景,模式就不会空。
四、推荐的学习顺序
你可以按这个顺序学,不分“重要不重要”,而是按理解难度和知识衔接来排。
第一阶段:Rust 基础表达型模式
先学这些,因为它们和 Rust 基础语法关系最紧。
迭代器模式
过滤器模式
原型模式
单例模式
构建者模式
工厂模式
这一阶段重点理解:
Iterator 怎么组织数据处理流程
Clone 怎么表达对象复制
OnceLock 怎么表达全局初始化
Builder 怎么构造复杂对象
关联函数 new() 怎么替代一部分工厂类
第二阶段:trait 抽象型模式
然后学这些,因为它们都依赖 trait。
策略模式
模板方法模式
适配器模式
代理模式
装饰器模式
桥接模式
这一阶段重点理解:
trait 是 Rust 中行为抽象的核心
泛型适合编译期确定类型
dyn Trait 适合运行时选择对象
包装结构体可以扩展已有对象功能
你要特别注意 T: Trait 和 Box<dyn Trait> 的区别。
前者更像“编译期多态”,后者更像“运行时多态”。
第三阶段:结构组织型模式
接着学对象之间怎么组织。
外观模式
组合模式
享元模式
中介者模式
这一阶段重点理解:
如何把多个模块封装成一个简单接口
如何表达树形结构
如何复用共享对象
如何让对象不直接互相依赖,而是通过中间层通信
这部分会涉及一些 Rust 智能指针,比如:
Box
Rc
Arc
RefCell
Mutex
但不要一开始就陷入智能指针细节。先理解它们解决的是:
谁拥有数据?
谁可以共享数据?
谁可以修改数据?
这个修改发生在单线程还是多线程?
第四阶段:行为协作型模式
然后学习对象之间的行为流转。
命令模式
观察者模式
责任链模式
备忘录模式
这一阶段重点理解:
命令模式:把行为变成可以保存、传递、执行的对象
观察者模式:一个事件发生后通知多个订阅者
责任链模式:请求经过多个处理节点
备忘录模式:保存某一时刻的状态,之后可以恢复
这部分很适合结合实际项目来理解,比如:
日志系统
Web 请求中间件
事件通知系统
任务调度系统
撤销重做系统
第五阶段:状态与解释型模式
最后学这些,因为它们更抽象。
状态模式
解释器模式
访问者模式
这一阶段重点理解:
状态模式:对象在不同状态下行为不同
解释器模式:定义一套表达式结构并执行它
访问者模式:在不修改数据结构的情况下添加操作
Rust 里这部分经常和 enum + match 结合。
尤其是状态模式,在 Rust 里非常适合用:
enum
match
类型状态模式
来表达。
五、每个模式都用同一个笔记模板
建议你建一个固定模板,每学一个模式都这样记录:
模式名称:
它解决的问题:
典型使用场景:
传统 OOP 的实现思路:
Rust 的实现思路:
Rust 中常用到的语法:
- struct
- trait
- enum
- match
- 泛型
- dyn Trait
- Rc / Arc
- RefCell / Mutex
它和其他模式的区别:
什么时候不应该用:
一个自己的例子:
这个模板比单纯看文章效果好很多。
六、不要把 Rust 设计模式学成 Java 设计模式
这是最关键的一点。
很多人学 Rust 设计模式,会犯一个错误:
先记住 Java 的设计模式,再强行翻译成 Rust。
这样会写出很别扭的 Rust 代码。
Rust 的思路应该是:
能用 enum,就不要急着用 trait object。
能用 match,就不要急着搞访问者。
能用泛型,就不要急着用 dyn Trait。
能用组合,就不要模拟继承。
能用所有权表达生命周期,就不要乱用全局状态。
能用 Result,就不要模拟异常。
能用 Option,就不要制造空指针。
所以学习时要不断问:
这个模式在 Rust 里是自然写法,还是硬套写法?
七、设计模式之间要成组理解
不要孤立地学每一个模式。很多模式很像,要放在一起比较。
例如:
策略模式、状态模式、命令模式
它们都和“行为变化”有关。
区别是:
策略模式:重点是算法可以替换
状态模式:重点是对象状态变化后行为变化
命令模式:重点是把一次操作封装起来
再比如:
代理模式、装饰器模式、适配器模式、外观模式
它们都像是在“包一层”。
区别是:
代理模式:控制访问
装饰器模式:增加功能
适配器模式:转换接口
外观模式:简化复杂系统
再比如:
工厂模式、构建者模式、原型模式、单例模式
它们都和“对象创建”有关。
区别是:
工厂模式:根据条件创建对象
构建者模式:一步步构造复杂对象
原型模式:通过克隆已有对象创建新对象
单例模式:全局只初始化一个对象
这样成组理解,记忆会清楚很多。
八、最终目标
学习 Rust 设计模式的最终目标不是把所有模式名字背下来,而是形成这种判断能力:
看到复杂构造过程,想到 Builder。
看到算法切换,想到 Strategy。
看到状态流转,想到 enum State。
看到事件通知,想到 Observer。
看到请求层层处理,想到 Chain of Responsibility。
看到接口不兼容,想到 Adapter。
看到需要简化复杂模块,想到 Facade。
看到要保存历史状态,想到 Memento。
看到要表达树结构,想到 Composite。
看到要解释表达式,想到 Interpreter。
然后再进一步判断:
这个模式在 Rust 中该用 trait?
还是 enum?
还是泛型?
还是 dyn Trait?
还是 Iterator?
还是智能指针?
这才是 Rust 设计模式真正的学习方式。