
文档技术博客教程【免费下载链接】weekly前端精读周刊。帮你理解最前沿、实用的技术。项目地址https://gitcode.com/GitHub_Trending/we/weekly点击查看免费下载导读Memento备忘录模式是行为型设计模式中专门解决对象内部状态的捕获与恢复问题的方案。本文以《前端精读周刊》184.精读《设计模式 - Memoto 备忘录模式》为骨架结合仓库中可视化搭建内置 API 对canUndo/canRedo的真实设计、Command 命令模式 中对撤销重做的分析讲清楚备忘录模式的定义、三大典型应用场景、完整 TypeScript 实现以及它与 Redux、画布撤销重做等现代前端状态管理的内在联系。读完你将掌握如何用统一 API 封装任意复杂对象的状态存取并能在编辑器、游戏存档、富文本草稿等场景中落地实现。什么是备忘录模式Memento备忘录模式属于行为型模式是针对如何捕获与恢复对象内部状态这一问题的设计模式。意图在不破坏封装性的前提下捕获一个对象的内部状态并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。备忘录模式的思想非常简单其核心是定义了一个 Memento备忘录封装对象由这个对象处理原始对象的状态捕获与还原其他地方不需要感知原始对象的内部数据结构和实现原理Memento 对象本身结构也非常简单只有getState与setState一存一取两个方法任何类型的对象状态都可以塞进同一个备忘录对象里从而对外暴露统一的存取 API。三个典型应用场景设计模式需要在日常工作里用起来结合例子可以加深理解。下面三个例子分别对应画布、游戏、文档三种常见业务。撤销重做如果撤销重做涉及到大量复杂对象且每个对象内部状态的存储结构都不同一个一个处理很容易写出 case by case 的冗余代码而且在拓展一种新对象结构时如嵌入 PPT还需要在撤销重做时对相应结构做专门处理。备忘录思维相当于一种统一封装思维不管对象结构如何都可以保存在一个 Memento 对象中通过setState设置对象状态、getState获取对象状态。这样对于任何类型的对象画布都可以通过统一的 API 操作进行存取了。在仓库的可视化搭建内置 API 设计中这一思路被直接应用内置状态canUndo、canRedo类型boolean评价为建议有描述当前状态是否能撤销或重做并配套内置方法undo()、redo()一起提供。文档特别指出如果应用分了多个 sheet、每个 sheet 内是一个画布实例且撤销重做需要跨 sheet就不适合用单实例提供的方法——这正是备忘录统一封装 由管理者Caretaker决定如何组织多份状态思想的体现底层每份快照就是备忘录而上层按 sheet 还是按全局组织则由管理者策略决定。游戏保存许多游戏支持设置与读取多种存档。转换为代码模式我们希望有这样一种 API 进行多存档管理// 创建一盘游戏。 const game new Game() // 玩一会。 game.play() // 设置一个存档(archive) 1。 const gameArchive1 game.createArchive() // 再玩一会。 game.play() // 设置一个存档(archive) 2。 const gameArchive2 game.createArchive() // 再玩一会。 game.play() // 这个时候角色挂了提示 “请读取存档”玩家此时选择了存档 1。 game.loadArchive(gameArchive1) // 此时游戏恢复存档 1 状态又可以愉快的玩耍了。在游戏保存的例子中存档就是备忘录Memento。主进程管理游戏状态时只是简单调用了createArchive创建存档与loadArchive读取存档即可实现复杂的游戏保存与读取功能全程不需要关心游戏内部状态到底有多少、这么多状态需要如何一一恢复——这正是得益于备忘录模式的设计。文章草稿保存富文本编辑器的文档草稿保存原理相同简单场景只需要一个 Memento 对象即可若要实现复杂一点的多版本状态管理只需要类似游戏保存机制存储多个 Memento 存档即可。意图解释与 Redux 和前端状态管理的类比看到这里会发现备忘录模式与前端状态管理的保存与恢复非常像。以 Redux 类比setState就像reducer处理后的最终state状态一样。对 Redux 全局状态来说它不用关心业务逻辑有多少reducer、每个reducer做了什么它只需要知道任何reducer最后处理完后都是一个state对象将其生成出来并存下来即可。恢复也是一样initState就类似getState只要将上一次生成的state灌进来就可以完全还原某个时刻的状态而不需要关心这个状态内部是怎样的。所以备忘录模式其实早已得到广泛应用。仔细理解后会发现没必要抠得太细原始设计模式如何定义因为经过几十年的演化这些设计模式思路早已融入了编程框架的方方面面。但依照惯例还是咬文嚼字解释一下意图意图在不破坏封装性的前提下捕获一个对象的内部状态并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。重点在于不破坏封装性这几个字上。程序的可维护性永远是设计模式关注的重点——无论是游戏存档的例子还是 Redux 的例子上层框架使用状态时都不需要知道具体对象状态的细节而实现这一点的就是 Memento 这个抽象的备忘录类。从仓库中Command 命令模式 的阐述也能看到互补关系命令模式如果记录下反向操作信息就可以实现撤销重做了它强调的是操作的序列化而备忘录模式强调的是状态快照的保存。实际成熟的撤销重做系统往往两者兼用命令描述怎么改备忘录描述改成什么样。结构图备忘录三剑客备忘录模式由三个角色组成Originator发起者创建、读取备忘录的对象。它是状态的真正持有者负责生成包含当前状态的备忘录并在需要时用备忘录还原自身状态。Memento备忘录专门存储原始对象状态并且防止 Originator 之外的对象读取其内部细节。Caretaker管理者备忘录管理者一般用数组或链表管理一堆备忘录在撤销重做或者版本管理时会用到。三者职责清晰Originator 只管状态与快照互转Memento 只管装状态Caretaker 只管存多少份、按什么顺序存取。代码例子TypeScript 完整实现下面例子使用 TypeScript 编写是备忘录模式三剑客的定义// 备忘录专门存储原始对象状态 class Memento { public state: any constructor(state: any) { this.state state } public getState() { return this.state } } // 备忘录管理者用数组栈管理一堆备忘录 class Caretaker { private stack: Memento[] [] public getMemento() { return this.stack.pop() } public addMemento(memoto: Memento) { this.stack.push(memoto) } } // 发起者创建、读取备忘录 class Originator { private state: any public getState() { return this.state } public setState(state: any) { this.state state } public createMemoto() { return new Memento(this.state) } public setMemoto(memoto: Memento) { this.state memoto.getState() } }下面是一个简化版客户端使用的例子// 实例化发起者比如画布、文章管理器、游戏管理器 const originator new Originator() // 实例化备忘录管理者 const caretaker new Caretaker() // 设置状态分别对应 // 画布的组件操作。 // 文章的输入。 // 游戏的 .play() originator.setState(hello world) // 备忘录管理者记录一次状态分别对应 // 画布的保存。 // 文章的保存。 // 游戏的保存。 caretaker.addMemento(originator.createMemoto()) // 从备忘录管理者还原状态分别对应 // 画布的还原。 // 文章的读取。 // 游戏读取存档。 originator.setMemoto(caretaker.getMemento())按需选择备忘录的组织结构在上面的例子中备忘录管理者存储状态用的是数组栈所以可以实现撤销重做如果要实现任意读档可以将备忘录存储结构变为Map按照key来读取如果没有这些要求存一个单一的 Memento 也够用了。这一点与仓库中可视化搭建内置 API 的取舍逻辑一致当业务只需要当前是否能撤销/重做时用栈式管理者即可当需要跨 sheet 或按 key 定位历史状态时就需要更复杂的管理策略——备忘录模式把状态存储和状态管理策略解耦开正是为了适应这种弹性需求。弊端备忘录模式存储的是完整状态而非 Diff所以可能会在运行时消耗大量内存。当然在 Immutable 模式下通过引用共享可以极大程度缓解这个问题仓库中精读《Immutable 结构共享》 正是对引用共享缓解快照内存这一思路的深入解读。另外备忘录模式已经很大程度上被融合到现代框架中你在使用状态管理工具时就已经在使用备忘录模式了所以很多情况下不需要机械地按照上面的代码例子使用。设计模式的重点在于利用它优化程序的可维护性而不是强求使用方式和官方描述一模一样。总结备忘录模式通过备忘录对象将对象内部状态封装了起来简化了程序复杂度符合设计模式一贯遵循的高内聚、低耦合原则。其实践行备忘录模式最好的例子就是 Redux当项目所有状态都使用 Redux 管理时你会发现无论是撤销重做还是保存读取都可以非常轻松完成。这时候不要质疑为什么备忘录模式还在解决这种遇不到的问题因为 Redux 本身就包含了备忘录设计模式的理念。在仓库中这一理念同样渗透进了可视化搭建内置 API 的undo()/redo()与 Command 命令模式 的撤销重做分析中无论组件树多复杂、业务状态多多样只要统一抽象出快照与恢复两个动作上层使用者就永远只需要面对同一套 API——这正是备忘录模式历经几十年演化后依然活跃在前端工程中的根本原因。延伸阅读设计模式 - Command 命令模式命令模式与备忘录模式在撤销重做上的互补关系设计模式 - Observer 观察者模式同属行为型模式的另一经典可视化搭建内置 APIcanUndo/canRedo/undo/redo的真实框架设计精读《Immutable 结构共享》引用共享缓解快照内存问题的原理赞分享文档技术博客教程【免费下载链接】weekly前端精读周刊。帮你理解最前沿、实用的技术。项目地址https://gitcode.com/GitHub_Trending/we/weekly点击查看免费下载相关推荐CS-Notes 设计模式精讲备忘录Memento模式——不破坏封装的状态备份与恢复实战CS Notes 设计模式精讲备忘录Memento模式——不破坏封装的状态备份与恢复实战 备忘录Memento模式是 GoF 行为型设计模式之一其核知识库文档教程Java 设计模式之 Memento备忘录模式在不破坏封装的前提下实现对象状态快照与撤销恢复Java 设计模式之 Memento备忘录模式在不破坏封装的前提下实现对象状态快照与撤销恢复 本文以 GitHub 推荐项目精选 / ja / java示例工程教程Memento 备忘录模式实战java-design-patterns 恒星快照示例中的状态捕获与撤销回滚Memento 备忘录模式实战java design patterns 恒星快照示例中的状态捕获与撤销回滚 本篇文章以 java design pattern示例工程教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考