Java 23 种设计模式:从踩坑到精通 | 番外:备忘录模式 —— 订单草稿快照实战
Java 23 种设计模式:从踩坑到精通 | 番外:备忘录模式 —— 订单草稿快照实战
摘要:备忘录模式在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复。它通过“发起人自己保存、自己恢复”的机制,在不暴露实现细节的前提下实现撤销和回滚。本文结合电商订单草稿自动保存与恢复的场景,完整展示如何用备忘录模式实现任意历史版本的回退,并与命令模式、原型模式深度对比,帮你掌握“状态快照”的设计精髓。
🗺️ 本文阅读地图(3 分钟速览)
- 为什么不能直接把订单状态保存到外部?
- 备忘录三角色:发起人、备忘录(不可变)、管理者
- 手写草稿管理器:自动保存历史版本,支持回退
- 备忘录 vs 命令:状态快照 vs 操作记录
- 面试必问:“管理者能修改备忘录吗?为什么备忘录要不可变?”
📖 《Java 23 种设计模式:从踩坑到精通》
开篇:系列介绍与目录 | 正篇:Memento 备忘录模式 —— 快照与撤销,给对象装一个“后悔药” | 当前:番外 · 备忘录模式 × 订单草稿快照
🔗 返回系列总目录
1. 订单草稿自动保存的痛点
在电商系统中,用户填写订单草稿时,系统需要定时保存历史版本,支持“撤销”回退到任意历史版本。如果直接把订单状态保存到外部变量:
// 直接把内部状态暴露给外部
Map<String, String> history = new HashMap<>();
history.put("v1", order.getState()); // 破坏了封装性
这种方式迫使订单类暴露内部状态,破坏了封装性。更糟糕的是,外部代码可以随意修改保存的状态,导致快照被污染。
备忘录模式的解决思路:让订单类(发起人)自己创建状态快照(备忘录),管理者只负责存取快照,无法查看或修改快照内容。恢复时,订单类根据快照自行恢复。
1.1 你的场景该不该用备忘录?
| 判断标准 | 是 → 用备忘录 | 否 → 用其他方式 |
|---|---|---|
| 需要保存对象的历史状态,支持撤销/回退 | ✅ | ❌ |
| 希望不破坏封装地实现状态保存 | ✅ | ❌ |
| 对象状态复杂,不能简单用几个变量记录 | ✅ | ❌ |
| 对象状态简单,只需记录一两个字段 | ❌ | 直接保存字段值即可 |
2. 备忘录模式 UML(订单草稿快照场景)

3. 完整源码实现
3.1 备忘录类 (Memento)
/**
* 备忘录:存储发起人的状态快照
* 对外仅暴露窄接口(只能获取状态,不能修改)
*/
public class Memento {
private final String state; // 状态快照,不可变
public Memento(String state) {
this.state = state;
}
public String getState() { return state; }
}
💬 白话:备忘录是一个“只读快照”——
state字段是final,一旦创建就不能修改。这就是为什么管理者无法污染历史版本。
3.2 发起人:订单草稿 (Originator)
/**
* 发起人:订单草稿类,负责创建和恢复备忘录
*/
public class Originator {
private String state; // 当前草稿内容
private String orderId;
public Originator(String orderId) {
this.orderId = orderId;
this.state = "";
}
/** 创建当前状态的备忘录快照 */
public Memento createMemento() {
System.out.println("💾 保存草稿快照:" + state);
return new Memento(state);
}
/** 从备忘录恢复之前的状态 */
public void restore(Memento memento) {
this.state = memento.getState();
System.out.println("🔄 已恢复到快照状态:" + state);
}
/** 修改状态(模拟用户编辑草稿) */
public void setState(String state) {
this.state = state;
System.out.println("✏️ 编辑草稿内容:" + state);
}
public String getState() { return state; }
public String getOrderId() { return orderId; }
}
💬 白话:订单草稿自己知道该保存什么状态、怎么恢复。外部完全不知道草稿内部有哪些字段——这就是封装性的关键。
3.3 管理者:草稿管理器 (Caretaker)
import java.util.*;
/**
* 管理者:草稿管理器,负责存储和保管备忘录
*/
public class Caretaker {
private List<Memento> mementos = new ArrayList<>();
private String orderId;
public Caretaker(String orderId) {
this.orderId = orderId;
}
public void add(Memento memento) {
mementos.add(memento);
System.out.println("📦 草稿已保存到历史版本,当前共 " + mementos.size() + " 个版本\n");
}
public Memento get(int index) {
if (index < 0 || index >= mementos.size()) {
throw new IndexOutOfBoundsException("版本索引超出范围");
}
return mementos.get(index);
}
public Memento getLatest() {
return mementos.isEmpty() ? null : mementos.get(mementos.size() - 1);
}
public void remove(int index) { mementos.remove(index); }
public int getVersionCount() { return mementos.size(); }
}
💬 白话:管理者就像“档案管理员”——只负责存取档案,从不打开看内容。它用的是
List,可以按索引回退到任意版本。
3.4 客户端测试
public class Client {
public static void main(String[] args) {
Originator order = new Originator("20240723001");
Caretaker caretaker = new Caretaker("20240723001");
System.out.println("=== 场景:用户编辑订单草稿,系统自动保存历史版本 ===\n");
// v1
order.setState("商品:iPhone 15, 数量:1");
caretaker.add(order.createMemento());
// v2
order.setState("商品:iPhone 15, 数量:2, 颜色:黑色");
caretaker.add(order.createMemento());
// v3
order.setState("商品:iPhone 15, 数量:2, 颜色:黑色, 赠品:耳机");
caretaker.add(order.createMemento());
System.out.println("=== 场景:用户误操作,需要撤销到版本1 ===\n");
// 恢复到版本1
order.restore(caretaker.get(0));
System.out.println("\n=== 场景:用户重新编辑,保存新版本 ===\n");
order.setState("商品:iPhone 15, 数量:1, 颜色:白色");
caretaker.add(order.createMemento());
System.out.println("\n=== 最终状态 ===");
System.out.println("当前草稿内容:" + order.getState());
System.out.println("历史版本总数:" + caretaker.getVersionCount());
}
}
4. 运行结果
=== 场景:用户编辑订单草稿,系统自动保存历史版本 ===
✏️ 编辑草稿内容:商品:iPhone 15, 数量:1
💾 保存草稿快照:商品:iPhone 15, 数量:1
📦 草稿已保存到历史版本,当前共 1 个版本
✏️ 编辑草稿内容:商品:iPhone 15, 数量:2, 颜色:黑色
💾 保存草稿快照:商品:iPhone 15, 数量:2, 颜色:黑色
📦 草稿已保存到历史版本,当前共 2 个版本
✏️ 编辑草稿内容:商品:iPhone 15, 数量:2, 颜色:黑色, 赠品:耳机
💾 保存草稿快照:商品:iPhone 15, 数量:2, 颜色:黑色, 赠品:耳机
📦 草稿已保存到历史版本,当前共 3 个版本
=== 场景:用户误操作,需要撤销到版本1 ===
🔄 已恢复到快照状态:商品:iPhone 15, 数量:1
=== 场景:用户重新编辑,保存新版本 ===
✏️ 编辑草稿内容:商品:iPhone 15, 数量:1, 颜色:白色
💾 保存草稿快照:商品:iPhone 15, 数量:1, 颜色:白色
📦 草稿已保存到历史版本,当前共 4 个版本
=== 最终状态 ===
当前草稿内容:商品:iPhone 15, 数量:1, 颜色:白色
历史版本总数:4
5. 核心角色回顾
| 角色 | 职责 | 对应代码 |
|---|---|---|
| Originator | 创建快照、恢复快照 | Originator |
| Memento | 不可变快照,封装内部状态 | Memento |
| Caretaker | 存取快照,不查看不修改 | Caretaker |
6. 备忘录模式 vs 命令模式 vs 原型模式
| 对比项 | 备忘录模式 | 命令模式 | 原型模式 |
|---|---|---|---|
| 核心目的 | 保存/恢复对象状态 | 封装请求为对象 | 克隆对象 |
| 是否保存状态 | 是,主动保存快照 | 可选,通过命令存储操作前状态 | 是,创建副本 |
| 典型操作 | createMemento() / restore() |
execute() / undo() |
clone() |
| 典型应用 | 订单草稿回退、游戏存档 | 仓储设备控制、事务回滚 | 报表模板复制 |
💡 一句话记忆:备忘录是“拍照留底”,命令是“执行+反悔”,原型是“复制粘贴”。三种模式常配合使用——命令负责记录操作历史,备忘录保存操作前状态,原型通过克隆快速创建快照。
7. 备忘录模式的优缺点
| 优点 | 缺点 |
|---|---|
| 不破坏封装:内部状态对外界完全隐藏 | 频繁保存快照会消耗大量内存 |
| 可恢复性强:支持任意历史版本回退 | 快照保存整个对象状态,大对象有性能开销 |
| 三角色职责清晰 | 管理者“无知”——只能存取,不能查看 |
8. 六大设计原则体现
| 原则 | 体现 |
|---|---|
| 单一职责 | 发起人管业务,备忘录管快照,管理者管存储 |
| 开闭原则 | 新增保存策略无需修改发起人 |
| 里氏替换 | 不同备忘录实现可替换 |
| 依赖倒置 | 管理者依赖抽象 Memento |
| 接口隔离 | 备忘录对管理者只暴露窄接口(getState) |
| 迪米特法则 | 管理者只与备忘录交互,不知发起人存在 |
附 备忘录模式 UML源码(订单草稿快照场景)
@startuml
title Java 23 种设计模式:从踩坑到精通
footer 折哥 | 智能物流与Java实战
' 1. 全局样式配置
skinparam backgroundColor #FEFEFE
skinparam shadowing false
skinparam classBorderColor #333333
skinparam classFontColor #1A1A1A
skinparam classFontSize 14
skinparam noteFontSize 12
skinparam noteFontColor #555555
skinparam arrowColor #555555
skinparam classBackgroundColor #F9F9F9
' 2. 发起人
class Originator {
- state : String
+ createMemento() : Memento
+ restore(Memento) : void
}
note right of Originator
<b>发起人</b>
--
创建备忘录保存自身状态快照
通过备忘录恢复之前的状态
end note
' 3. 备忘录(不可变快照)
class Memento {
- state : String
+ getState() : String
}
note right of Memento
<b>备忘录</b>
--
存储发起人的内部状态快照
状态对外部不可见(窄接口)
保证封装性不被破坏
end note
' 4. 管理者
class Caretaker {
- mementos : List<Memento>
+ add(Memento) : void
+ get(int) : Memento
+ remove(int) : void
}
note right of Caretaker
<b>管理者</b>
--
负责存储和保管备忘录
不能修改或查看备忘录内容
仅提供存取操作
end note
' 5. 关系连线
Originator ..down> Memento : 创建
Caretaker o-down-> Memento : 持有
@enduml
🧭 《Java 23 种设计模式:从踩坑到精通》快速导航
- 开篇:系列介绍与目录
- 正篇:Memento 备忘录模式 —— 快照与撤销,给对象装一个“后悔药”
- 当前:番外 · 备忘录模式 × 订单草稿快照(你在这里)
- 创建型模式汇总
- 结构型模式汇总
- 行为型模式汇总
🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦 福利预告:全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。
📌 除了设计模式,我也在深挖智能物流实战(WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》、《电商多平台电子面单对接实战》。技术相通,思路可鉴。
更多推荐





所有评论(0)