备忘录模式
引言
备忘录模式是一种行为型设计模式,用于在不破坏对象封装性的前提下,捕获并保存对象的内部状态,以便在未来恢复到该状态。想象一个文本编辑器,支持“撤销”功能,每次编辑操作都会保存当前文档状态,允许用户回滚到之前的版本。备忘录模式的核心思想是将对象的状态存储在一个外部“备忘录”对象中,并通过“管理者”控制状态的保存与恢复。它在保证封装性的同时,提供了灵活的状态管理机制,广泛应用于需要历史记录、撤销重做或状态快照的场景。它的魅力在于让状态管理既安全又高效,完美平衡了灵活性与封装性。
实际开发中的用途
备忘录模式在实际开发中适用于需要保存和恢复对象状态的场景,尤其在以下场景中表现出色:
- 撤销/重做功能:如文本编辑器、图形编辑软件或 IDE,支持用户回滚操作。
- 事务管理:在数据库操作或业务流程中,保存中间状态以便在失败时回滚。
- 游戏开发:保存玩家的游戏进度(如关卡、分数)以便恢复。
- 配置管理:在系统配置修改时,保存旧配置以便在出错时恢复。
以电商系统的购物车为例,假设用户在添加商品到购物车后可能需要撤销操作。如果直接在购物车对象中维护状态历史,代码会变得复杂且违背单一职责原则。使用备忘录模式,可以将购物车的状态(如商品列表、数量)保存到备忘录对象中,由一个管理者(如 CartHistory
)负责状态的存储与恢复。这种方式不仅解耦了状态管理逻辑,还便于扩展(如支持多级撤销)。备忘录模式的核心价值在于提供了一种优雅的状态管理方式,让系统更具灵活性和用户友好性。
示例场景:一个在线文档编辑系统,用户编辑文档后可撤销到上一次保存的状态。每次编辑操作都会生成一个备忘录,保存当前的文档内容,撤销时直接恢复到指定状态,逻辑清晰且用户体验流畅。
Spring 源码中的应用
在 Spring 框架中,备忘录模式的典型应用体现在 Spring WebFlux 的 WebSession
管理中。WebSession
用于在 Web 应用中保存用户会话状态,允许在请求间持久化数据(如用户偏好、临时数据)。Spring WebFlux 使用类似备忘录模式的方式,通过 WebSession
保存会话状态的快照,并在需要时恢复。
源码分析
以下是 Spring WebFlux 中 WebSession
的相关源码片段,摘自 org.springframework.web.server.session
包中的 DefaultWebSessionManager
public class DefaultWebSessionManager implements WebSessionManager {
private WebSessionStore sessionStore = new InMemoryWebSessionStore();
@Override
public Mono<WebSession> getSession(ServerWebExchange exchange) {
return Mono.defer(() -> {
// 从存储中获取或创建会话
String sessionId = resolveSessionId(exchange);
return (sessionId != null ?
this.sessionStore.retrieveSession(sessionId) :
this.sessionStore.createWebSession())
.doOnNext(session -> exchange.getResponse().beforeCommit(() ->
saveSession(session, exchange)));
});
}
private Mono<Void> saveSession(WebSession session, ServerWebExchange exchange) {
// 保存会话状态到存储
if (!session.isStarted() || session.isExpired()) {
return Mono.empty();
}
return this.sessionStore.updateSession(session);
}
}
分析:
- 备忘录角色:
WebSession
充当备忘录,存储会话的当前状态(如属性键值对)。 - 发起人角色:
DefaultWebSessionManager
作为发起人,负责创建和恢复会话状态。 - 管理者角色:
WebSessionStore
(如InMemoryWebSessionStore
)作为管理者,负责存储和检索备忘录对象。 - 模式体现:
WebSession
通过getSession
获取会话状态,并在请求结束时通过saveSession
保存状态快照。状态的存储和恢复完全由WebSessionStore
管理,WebSession
本身无需关心存储细节,保证了封装性。 - 问题解决:该机制解耦了会话状态的管理与存储逻辑,支持分布式会话(如通过 Redis 存储)和动态恢复(如用户重新登录后恢复会话)。它广泛应用于 Spring WebFlux 的无状态 Web 应用中。
这种设计让 Spring WebFlux 在处理高并发 Web 请求时,既能高效管理会话状态,又能保证状态的隔离性和安全性,体现了备忘录模式在企业级场景中的强大能力。
Spring Boot 代码案例
以下是一个基于 Spring Boot 的案例,模拟一个在线文档编辑系统,通过备忘录模式实现文档的编辑与撤销功能。案例展示了如何利用备忘录模式保存和恢复文档状态,解决状态管理的复杂性。
案例背景
在一个在线文档编辑系统中,用户可以编辑文档内容,并支持撤销操作。每次编辑都会保存文档的当前状态,撤销时恢复到上一次的状态。
代码实现
// 备忘录类,保存文档状态
public class DocumentMemento {
private final String content;
public DocumentMemento(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
// 发起人类,管理文档内容
public class DocumentEditor {
private String content;
public DocumentEditor() {
this.content = "";
}
public void edit(String newContent) {
this.content = newContent;
}
public DocumentMemento save() {
return new DocumentMemento(content);
}
public void restore(DocumentMemento memento) {
this.content = memento.getContent();
}
public String getContent() {
return content;
}
}
// 管理者类,负责存储备忘录
@Service
public class DocumentHistory {
private final Stack<DocumentMemento> history = new Stack<>();
public void save(DocumentMemento memento) {
history.push(memento);
}
public DocumentMemento undo() {
if (!history.isEmpty()) {
return history.pop();
}
return null;
}
}
// 文档服务
@Service
public class DocumentService {
private final DocumentEditor editor;
private final DocumentHistory history;
public DocumentService(DocumentEditor editor, DocumentHistory history) {
this.editor = editor;
this.history = history;
}
public void editDocument(String content) {
// 保存当前状态
history.save(editor.save());
// 编辑新内容
editor.edit(content);
}
public String undo() {
DocumentMemento memento = history.undo();
if (memento != null) {
editor.restore(memento);
return editor.getContent();
}
return editor.getContent();
}
public String getCurrentContent() {
return editor.getContent();
}
}
// Spring Boot 主应用
@SpringBootApplication
public class MementoPatternApplication {
public static void main(String[] args) {
SpringApplication.run(MementoPatternApplication.class, args);
}
@Bean
public DocumentEditor documentEditor() {
return new DocumentEditor();
}
}
// 测试控制器
@RestController
@RequestMapping("/documents")
public class DocumentController {
private final DocumentService documentService;
public DocumentController(DocumentService documentService) {
this.documentService = documentService;
}
@PostMapping("/edit")
public String editDocument(@RequestBody Map<String, String> request) {
String content = request.get("content");
documentService.editDocument(content);
return "Document updated: " + content;
}
@GetMapping("/undo")
public String undo() {
return documentService.undo();
}
@GetMapping("/current")
public String getCurrentContent() {
return documentService.getCurrentContent();
}
}
代码说明
- 备忘录模式体现:
DocumentMemento
保存文档的快照,DocumentEditor
负责创建和恢复状态,DocumentHistory
管理备忘录栈。DocumentService
协调编辑和撤销操作,利用 Spring 的依赖注入实现松耦合。 - 优势:
- 封装性:
DocumentMemento
只暴露getContent
方法,保护了状态的内部细节。 - 低耦合:状态管理与业务逻辑分离,易于扩展(如支持多级撤销或持久化历史)。
- 企业级适用性:结合 Spring 的 IoC 容器,代码结构清晰,适合集成到复杂系统中。
- 封装性:
- 运行效果:通过 POST
/documents/edit
编辑文档内容,GET/documents/undo
撤销操作,GET/documents/current
查看当前内容。每次编辑都会保存状态,撤销时恢复到上一次状态。
相似的设计模式对比
备忘录模式与 命令模式(Command Pattern)在某些场景下有相似之处,特别是在支持撤销操作时。以下是对比分析:
- 备忘录模式:
- 关键词:状态保存、恢复、封装、快照。
- 说明:专注于捕获和恢复对象的内部状态,强调状态的封装性和隔离性。
- 命令模式:
- 关键词:行为封装、撤销、操作记录、请求队列。
- 说明:将操作封装为命令对象,支持执行和撤销,强调行为的封装和操作历史。
对比表格
特性 | 备忘录模式 | 命令模式 |
---|---|---|
核心思想 | 保存和恢复对象状态 | 封装操作,支持执行和撤销 |
关注点 | 状态快照 | 行为封装 |
耦合度 | 较低,状态与逻辑分离 | 较低,命令与执行者分离 |
典型场景 | 撤销、重做、状态恢复 | 操作记录、事务管理、宏命令 |
Spring 中的实现 | WebSession(状态管理) | TransactionTemplate(事务操作) |
代码对比
以下通过一个简单文本编辑器功能对比两者的实现差异。
备忘录模式实现
// 备忘录类
public class TextMemento {
private final String text;
public TextMemento(String text) {
this.text = text;
}
public String getText() {
return text;
}
}
// 编辑器类
public class TextEditor {
private String text = "";
public void setText(String text) {
this.text = text;
}
public TextMemento save() {
return new TextMemento(text);
}
public void restore(TextMemento memento) {
this.text = memento.getText();
}
public String getText() {
return text;
}
}
// 管理者类
public class TextHistory {
private final Stack<TextMemento> history = new Stack<>();
public void save(TextMemento memento) {
history.push(memento);
}
public TextMemento undo() {
return history.isEmpty() ? null : history.pop();
}
}
命令模式实现
// 命令接口
public interface Command {
void execute();
void undo();
}
// 具体命令
public class EditCommand implements Command {
private final TextEditor editor;
private final String newText;
private String oldText;
public EditCommand(TextEditor editor, String newText) {
this.editor = editor;
this.newText = newText;
}
@Override
public void execute() {
oldText = editor.getText();
editor.setText(newText);
}
@Override
public void undo() {
editor.setText(oldText);
}
}
// 编辑器类
public class TextEditor {
private String text = "";
public void setText(String text) {
this.text = text;
}
public String getText() {
return text;
}
}
// 管理者类
public class CommandHistory {
private final Stack<Command> history = new Stack<>();
public void executeCommand(Command command) {
command.execute();
history.push(command);
}
public void undo() {
if (!history.isEmpty()) {
Command command = history.pop();
command.undo();
}
}
}
差异分析:
- 备忘录模式:
TextMemento
只保存状态快照,TextHistory
负责存储和恢复,适合单纯的状态管理场景。状态恢复逻辑简单,但不支持复杂操作。 - 命令模式:
EditCommand
封装了编辑操作和撤销逻辑,CommandHistory
管理命令执行和回滚,适合需要记录操作历史的场景。支持更复杂的撤销逻辑(如组合命令)。 - 适用性:备忘录模式更适合状态快照管理,命令模式更适合操作记录和复杂撤销。
总结
备忘录模式是一把状态管理的利剑,通过将对象状态封装到备忘录中,实现了安全、高效的状态保存与恢复。它的核心价值在于在不破坏封装性的前提下,提供灵活的状态管理机制,特别适合撤销、重做或会话管理等场景。在 Spring WebFlux 中,WebSession
的设计体现了备忘录模式的精髓,通过解耦状态存储与管理,助力高并发 Web 应用的开发。与命令模式相比,备忘录模式更专注于状态快照,简单而纯粹。开发者应善用其思想,在需要历史记录或状态恢复的场景中,打造优雅且用户友好的系统。备忘录模式如同一台时光机,让代码在过去与现在间自由穿梭,赋予系统无限可能。
(对您有帮助 && 觉得我总结的还行) -> 受累点个免费的赞👍,谢谢