备忘录模式核心解析
- 备忘录模式 (Memento Pattern):
- 核心目的是在不破坏对象封装性的前提下,捕获并保存对象的内部状态,以便后续需要时能将对象恢复到该状态;
- 可以用生活中的场景类比:
- 玩游戏时的 “存档”:游戏角色 (原发器) 的血量、等级、装备等状态被保存到存档文件 (备忘录),后续可以读档恢复状态;
- 文档编辑的 “撤销”:编辑器 (原发器) 每次输入后保存当前内容 (备忘录),点击撤销就能回到上一个状态;
- 类图:
- 实现代码:
// 1. 备忘录类(Memento):存储编辑器的状态,仅暴露获取状态的方法 class EditorMemento { private state: string; // 存储的编辑器内容(私有,外部无法修改) // 构造函数:初始化备忘录状态 constructor(state: string) { this.state = state; } // 获取存储的状态(给原发器恢复状态用) public getState(): string { return this.state; } } // 2. 原发器类(Originator):编辑器本身,拥有状态并能创建/恢复备忘录 class TextEditor { private content: string; // 编辑器当前内容(核心状态) constructor() { this.content = ""; } // 设置编辑器内容(修改状态) public setContent(content: string): void { this.content = content; console.log(`编辑器当前内容:${this.content}`); } // 获取当前内容(辅助方法) public getContent(): string { return this.content; } // 创建备忘录:保存当前状态 public createMemento(): EditorMemento { console.log("📌 保存当前编辑状态"); return new EditorMemento(this.content); } // 恢复备忘录:回到指定状态 public restoreMemento(memento: EditorMemento): void { this.content = memento.getState(); console.log(`🔙 恢复到历史状态:${this.content}`); } } // 3. 管理者类(Caretaker):管理备忘录(撤销栈),不操作状态仅存储 class UndoManager { private mementos: EditorMemento[]; // 存储所有备忘录的栈 constructor() { this.mementos = []; } // 添加备忘录到栈中(每次编辑后保存) public addMemento(memento: EditorMemento): void { this.mementos.push(memento); } // 获取指定索引的备忘录(撤销时取上一个) public getMemento(index: number): EditorMemento | undefined { return this.mementos[index]; } // 获取最后一个备忘录(简化撤销逻辑) public getLastMemento(): EditorMemento | undefined { return this.mementos.pop(); } } // 🌟 测试示例:模拟编辑器撤销操作 const editor = new TextEditor(); const undoManager = new UndoManager(); // 第一步:输入第一段内容,保存状态 editor.setContent("Hello World!"); undoManager.addMemento(editor.createMemento()); // 第二步:输入第二段内容,保存状态 editor.setContent("Hello World! 我是备忘录模式"); undoManager.addMemento(editor.createMemento()); // 第三步:输入第三段内容(错误输入) editor.setContent("Hello World! 我是备忘录模式(错误修改)"); // 第四步:撤销操作,恢复到上一个正确状态 const lastMemento = undoManager.getLastMemento(); if (lastMemento) { editor.restoreMemento(lastMemento); } // 第五步:再次撤销,恢复到最初状态 const firstMemento = undoManager.getLastMemento(); if (firstMemento) { editor.restoreMemento(firstMemento); }
常用使用场景
表单的撤销
-
业务场景:用户填写表单时误操作 (如删错内容、选错选项),支持撤销到上一步状态;
-
实现代码:
// 1. 备忘录:存储表单状态 class FormMemento { private state: Record<string, any>; // 表单状态(支持多字段) constructor(state: Record<string, any>) { // 深拷贝避免引用类型修改原数据 this.state = JSON.parse(JSON.stringify(state)); } getState(): Record<string, any> { return JSON.parse(JSON.stringify(this.state)); // 防止外部修改 } } // 2. 原发器:表单核心逻辑 class FormEditor { private formState: Record<string, any> = { username: "", phone: "", address: "", agree: false }; // 更新表单字段 updateField(field: string, value: any): void { this.formState[field] = value; console.log(`✅ 表单更新:${field} = ${value}`); console.log("当前表单状态:", this.formState); } // 创建备忘录 createMemento(): FormMemento { console.log("📌 保存表单状态"); return new FormMemento(this.formState); } // 恢复表单状态 restoreMemento(memento: FormMemento): void { this.formState = memento.getState(); console.log("🔙 撤销表单操作,恢复状态:", this.formState); } getFormState() { return this.formState; } } // 3. 管理者:管理表单撤销栈 class FormUndoManager { private mementos: FormMemento[] = []; addMemento(memento: FormMemento): void { this.mementos.push(memento); } getLastMemento(): FormMemento | undefined { return this.mementos.pop(); } } // 测试:表单撤销 console.log("===== 场景1:表单撤销 ====="); const form = new FormEditor(); const formUndo = new FormUndoManager(); // 步骤1:填写用户名 form.updateField("username", "zhangsan"); formUndo.addMemento(form.createMemento()); // 步骤2:填写手机号 form.updateField("phone", "13800138000"); formUndo.addMemento(form.createMemento()); // 步骤3:误填地址 form.updateField("address", "错误地址"); // 步骤4:撤销地址错误操作 const lastMemento = formUndo.getLastMemento(); if (lastMemento) form.restoreMemento(lastMemento);
页面状态恢复
-
业务场景:SPA 列表页筛选 / 排序后跳转到详情页,返回时恢复筛选、排序、页码状态;
-
实现代码:
// 1. 备忘录:存储列表页状态 class PageMemento { private state: { filter: Record<string, any>; // 筛选条件 sort: { key: string; order: "asc" | "desc" }; // 排序规则 page: number; // 页码 }; constructor(state: typeof PageMemento.prototype.state) { this.state = JSON.parse(JSON.stringify(state)); } getState() { return JSON.parse(JSON.stringify(this.state)); } } // 2. 原发器:列表页核心逻辑 class ListPage { private pageState = { filter: { keyword: "", category: "" }, sort: { key: "id", order: "asc" }, page: 1 }; // 更新筛选条件 updateFilter(filter: Record<string, any>): void { this.pageState.filter = { ...this.pageState.filter, ...filter }; console.log("筛选条件更新:", this.pageState.filter); } // 更新排序 updateSort(sort: { key: string; order: "asc" | "desc" }): void { this.pageState.sort = sort; console.log("排序规则更新:", this.pageState.sort); } // 更新页码 updatePage(page: number): void { this.pageState.page = page; console.log("页码更新:", page); } // 创建备忘录(离开页面时调用) createMemento(): PageMemento { console.log("📌 保存列表页状态"); return new PageMemento(this.pageState); } // 恢复备忘录(返回列表页时调用) restoreMemento(memento: PageMemento): void { this.pageState = memento.getState(); console.log("🔙 恢复列表页状态:", this.pageState); } } // 3. 管理者:全局存储页面状态(可结合localStorage持久化) class PageStateManager { private mementoMap: Record<string, PageMemento> = {}; // 页面路径 -> 备忘录 // 保存指定页面的状态 savePageState(pageKey: string, memento: PageMemento): void { this.mementoMap[pageKey] = memento; } // 获取指定页面的状态 getPageState(pageKey: string): PageMemento | undefined { return this.mementoMap[pageKey]; } } // 测试:页面状态恢复 console.log("\n===== 场景2:页面状态恢复 ====="); const listPage = new ListPage(); const pageManager = new PageStateManager(); // 步骤1:设置筛选和排序 listPage.updateFilter({ keyword: "手机", category: "数码" }); listPage.updateSort({ key: "price", order: "desc" }); listPage.updatePage(3); // 步骤2:跳转到详情页,保存列表页状态 pageManager.savePageState("/goods/list", listPage.createMemento()); // 步骤3:返回列表页,恢复状态 const savedState = pageManager.getPageState("/goods/list"); if (savedState) listPage.restoreMemento(savedState);
交互类页面的状态保存
-
业务场景:H5 答题游戏,用户关闭页面后重新打开,恢复答题进度、分数、当前关卡;
-
实现代码:
// 1. 备忘录:存储游戏状态 class GameMemento { private state: { level: number; // 当前关卡 score: number; // 分数 progress: number; // 答题进度(已答/总题数) answered: number[]; // 已答题目ID }; constructor(state: typeof GameMemento.prototype.state) { this.state = JSON.parse(JSON.stringify(state)); } getState() { return JSON.parse(JSON.stringify(this.state)); } } // 2. 原发器:游戏核心逻辑 class AnswerGame { private gameState = { level: 1, score: 0, progress: 0, answered: [] as number[] }; // 答题成功 answerSuccess(questionId: number, score: number): void { this.gameState.answered.push(questionId); this.gameState.score += score; this.gameState.progress = this.gameState.answered.length / 10; // 假设共10题 console.log(`答题成功:ID=${questionId},分数+${score},当前分数=${this.gameState.score}`); } // 升级关卡 nextLevel(): void { this.gameState.level += 1; console.log(`升级到${this.gameState.level}关`); } // 创建备忘录(持久化到localStorage) createMemento(): GameMemento { const memento = new GameMemento(this.gameState); // 模拟持久化存储 localStorage.setItem("gameState", JSON.stringify(memento.getState())); console.log("📌 保存游戏状态到本地"); return memento; } // 恢复备忘录(从localStorage加载) restoreMemento(memento?: GameMemento): void { if (!memento) { // 从本地加载 const saved = localStorage.getItem("gameState"); if (saved) this.gameState = JSON.parse(saved); } else { this.gameState = memento.getState(); } console.log("🔙 恢复游戏状态:", this.gameState); } } // 3. 管理者:游戏状态管理(简化版) class GameStateManager { private memento: GameMemento | null = null; saveState(memento: GameMemento): void { this.memento = memento; } getState(): GameMemento | null { return this.memento; } } // 测试:游戏状态保存 console.log("\n===== 场景3:交互类页面状态保存 ====="); const game = new AnswerGame(); const gameManager = new GameStateManager(); // 步骤1:答题升级 game.answerSuccess(1, 10); game.answerSuccess(2, 10); game.nextLevel(); // 步骤2:保存游戏状态 gameManager.saveState(game.createMemento()); // 步骤3:重新打开游戏,恢复状态 game.restoreMemento(gameManager.getState());
可视化组件的状态回退(拖拽组件)
-
业务场景:拖拽看板 (如 Trello 风格),拖拽卡片后支持撤销操作,恢复卡片位置;
-
实现代码:
// 1. 备忘录:存储看板状态 class KanbanMemento { private state: { cards: Record<string, { id: string; column: string; position: number }>; // 卡片位置 }; constructor(state: typeof KanbanMemento.prototype.state) { this.state = JSON.parse(JSON.stringify(state)); } getState() { return JSON.parse(JSON.stringify(this.state)); } } // 2. 原发器:看板核心逻辑 class KanbanBoard { private boardState = { cards: { card1: { id: "card1", column: "todo", position: 1 }, card2: { id: "card2", column: "todo", position: 2 } } }; // 移动卡片 moveCard(cardId: string, targetColumn: string, targetPos: number): void { if (this.boardState.cards[cardId]) { this.boardState.cards[cardId] = { ...this.boardState.cards[cardId], column: targetColumn, position: targetPos }; console.log(`移动卡片${cardId}到${targetColumn}列,位置${targetPos}`); console.log("当前看板状态:", this.boardState.cards); } } // 创建备忘录 createMemento(): KanbanMemento { console.log("📌 保存看板状态"); return new KanbanMemento(this.boardState); } // 恢复备忘录 restoreMemento(memento: KanbanMemento): void { this.boardState = memento.getState(); console.log("🔙 撤销拖拽,恢复看板状态:", this.boardState.cards); } } // 3. 管理者:看板撤销栈 class KanbanUndoManager { private mementos: KanbanMemento[] = []; addMemento(memento: KanbanMemento): void { this.mementos.push(memento); } getLastMemento(): KanbanMemento | undefined { return this.mementos.pop(); } } // 测试:看板拖拽撤销 console.log("\n===== 场景4:可视化组件状态回退 ====="); const kanban = new KanbanBoard(); const kanbanUndo = new KanbanUndoManager(); // 步骤1:保存初始状态 kanbanUndo.addMemento(kanban.createMemento()); // 步骤2:误移动卡片 kanban.moveCard("card1", "done", 1); // 步骤3:撤销拖拽操作 const lastKanbanState = kanbanUndo.getLastMemento(); if (lastKanbanState) kanban.restoreMemento(lastKanbanState);
购物车状态
-
业务场景:用户临时清空购物车后,支持恢复之前的购物车商品;或切换账号时暂存当前购物车状态;
-
实现代码:
// 1. 备忘录:存储购物车状态 class CartMemento { private state: { items: Array<{ id: string; name: string; count: number; price: number }>; // 购物车商品 selected: string[]; // 选中的商品ID }; constructor(state: typeof CartMemento.prototype.state) { this.state = JSON.parse(JSON.stringify(state)); } getState() { return JSON.parse(JSON.stringify(this.state)); } } // 2. 原发器:购物车核心逻辑 class ShoppingCart { private cartState = { items: [ { id: "p1", name: "手机", count: 1, price: 2999 }, { id: "p2", name: "耳机", count: 1, price: 199 } ], selected: ["p1", "p2"] }; // 添加商品 addItem(item: { id: string; name: string; count: number; price: number }): void { this.cartState.items.push(item); this.cartState.selected.push(item.id); console.log(`添加商品:${item.name},当前购物车:`, this.cartState.items); } // 清空购物车 clearCart(): void { this.cartState.items = []; this.cartState.selected = []; console.log("清空购物车:", this.cartState); } // 创建备忘录 createMemento(): CartMemento { console.log("📌 保存购物车状态"); return new CartMemento(this.cartState); } // 恢复备忘录 restoreMemento(memento: CartMemento): void { this.cartState = memento.getState(); console.log("🔙 恢复购物车状态:", this.cartState); } } // 3. 管理者:购物车状态管理(支持多版本) class CartStateManager { private mementos: Record<string, CartMemento> = {}; // 版本ID -> 备忘录 saveCartVersion(versionId: string, memento: CartMemento): void { this.mementos[versionId] = memento; } getCartVersion(versionId: string): CartMemento | undefined { return this.mementos[versionId]; } } // 测试:购物车状态恢复 console.log("\n===== 场景5:购物车状态 ====="); const cart = new ShoppingCart(); const cartManager = new CartStateManager(); // 步骤1:保存购物车初始版本 cartManager.saveCartVersion("v1", cart.createMemento()); // 步骤2:清空购物车(误操作) cart.clearCart(); // 步骤3:恢复购物车v1版本 const cartV1 = cartManager.getCartVersion("v1"); if (cartV1) cart.restoreMemento(cartV1);
优缺点分析
-
优点:
- 封装性好:不暴露对象内部结构;
- 简化发起者:将状态存储职责分离出去;
- 易于实现撤销/重做:天然支持操作历史;
- 状态恢复可靠:可以精确恢复到某个历史状态;
-
缺点:
- 内存消耗:如果状态过大或保存频繁,会消耗大量内存;
- 维护成本:需要维护备忘录的生命周期;
- 可能影响性能:频繁的序列化和反序列化;
实际应用建议
-
适合场景
- 需要保存对象历史状态的场景;
- 需要实现撤销功能的场景;
- 不希望暴露对象内部细节的场景;
-
优化策略
- 增量保存:只保存变化的部分;
- 压缩存储:对备忘录数据进行压缩;
- 限制历史数量:设置最大保存数量;
- 懒加载:按需恢复状态;
职责链模式(行为型模式)
上一篇