什么是备忘录模式?备忘录的应用

备忘录模式通过发起人、备忘录和负责人三者协作,实现对象状态的保存与恢复;发起人创建并恢复状态,备忘录存储状态且对外透明,负责人管理备忘录而不访问其内容,从而在不破坏封装性的前提下支持撤销、重做、游戏存档等场景。

什么是备忘录模式?备忘录的应用

备忘录模式,简单来说,就是一种在不破坏对象封装性的前提下,捕获并保存一个对象的内部状态,以便将来可以恢复到该状态的设计模式。它把对象的某个时刻的状态保存下来,就像拍了张快照。这听起来可能有点抽象,但想象一下你在玩游戏,随时可以存档、读档;或者在写文档,可以无限次地撤销、重做——这些功能背后,很可能就有备忘录模式的影子。它核心的价值在于,让我们能在需要的时候,把一个复杂对象“冻结”在某个特定状态,后续还能“解冻”回去,而这个过程对外部来说是透明的,不会暴露对象内部的实现细节。

备忘录模式的核心在于解耦:它将状态的保存和恢复逻辑从需要保存状态的对象(发起人 Originator)中分离出来,交给一个独立的备忘录对象(Memento)来承载。同时,还有一个负责人(Caretaker)来管理这些备忘录。

发起人(Originator)是那个拥有内部状态的对象,它知道如何创建自己的备忘录(即保存当前状态),也知道如何从备忘录中恢复状态。它就像一个艺术家,能够把自己的创作过程中的某个阶段“拍下来”,并能在需要时根据这张“照片”还原。

备忘录(Memento)则是一个纯粹的容器,用来存储发起人对象的内部状态。它通常对外提供一个窄接口,让负责人无法直接访问其内部细节,从而保护了发起人的封装性。对发起人而言,备忘录是透明的,可以访问其所有状态;但对负责人来说,备忘录就是个“黑盒子”,只能存储和传递,不能窥探。这种设计很巧妙,它确保了只有发起人自己能理解并操作自己的“快照”。

负责人(Caretaker)负责存储和管理备忘录对象。它从发起人那里获取备忘录,并在需要时将其还给发起人。负责人完全不知道备忘录内部到底存了些什么,它只管“存”和“取”,就像一个图书馆管理员,只负责图书的借阅和归还,而无需关心书本里的具体内容。

这种三角色协作的模式,完美地实现了状态保存与恢复的逻辑与业务逻辑的分离。我个人觉得,它最棒的地方就在于,它允许你在不污染原有业务逻辑代码的前提下,为对象增加“时间旅行”的能力。

备忘录模式解决的核心问题是什么?

我们为什么会需要备忘录模式?在我看来,它主要解决了一个非常实际且常见的痛点:如何在不打破对象封装性的前提下,保存并恢复其内部状态。设想一下,你有一个非常复杂的对象,比如一个图形编辑器中的画布对象,它包含了所有图层、图形、颜色、位置等信息。现在你需要实现“撤销”功能。

如果直接去访问画布对象的内部成员来保存状态,那无疑会打破它的封装性。一旦内部结构发生变化,所有依赖这些内部细节的保存/恢复代码都得跟着改,这简直是噩梦。而且,你可能需要保存很多个历史状态,手动管理这些状态的复杂性会迅速失控。

传统上,我们可能会想到深拷贝或者序列化。深拷贝固然能复制状态,但如果对象图很复杂,循环引用、资源管理等问题会让你头疼不已。序列化虽然可以将对象状态持久化,但它通常是为了跨进程或长期存储,对于内存中的频繁状态切换(比如撤销/重做)来说,性能开销可能过大,而且同样可能暴露内部结构。

备忘录模式的优雅之处在于,它把“如何保存状态”的细节封装在了发起人内部。发起人自己创建备忘录,自己从备忘录恢复。负责人只拿到一个“不透明”的备忘录对象,它不知道里面存了什么,也无法直接修改。这就像你把一个加密的盒子交给朋友保管,朋友只知道这是一个盒子,里面装了东西,但他打不开,只有你才能打开并取出里面的东西。这种设计完美地维护了对象的封装性,降低了耦合度。它让状态管理变得可控,而且对外部调用者来说,使用起来也极其简洁。

备忘录模式在实际开发中有哪些经典应用场景?

备忘录模式的应用场景其实非常广泛,只要涉及到“回溯”或“快照”功能的,它都是一个值得考虑的方案。

最经典的,无疑是撤销(Undo)和重做(Redo)功能。在任何文本编辑器、图形设计软件、CAD软件中,你进行的每一步操作都可以被撤销。每次操作完成后,软件可以创建一个当前状态的备忘录,并将其压入一个历史栈中。当用户点击“撤销”时,就从栈顶取出一个备忘录,恢复到上一个状态。这简直是备忘录模式的教科书式应用。

其次,游戏存档和进度保存也是备忘录模式的绝佳舞台。玩家在游戏中达到某个关键点,可以保存当前游戏状态,包括角色位置、物品、任务进度、敌人状态等等。这些复杂的内部状态被打包成一个或多个备忘录,然后可以序列化到文件或数据库中。下次玩家加载游戏时,就是从这些备忘录中恢复游戏世界。

在某些事务管理的场景中,备忘录模式也能发挥作用。比如,你可能需要执行一系列操作,但这些操作可能失败,或者需要提供回滚机制。你可以在操作开始前保存系统或关键对象的“快照”,如果操作失败,就利用备忘录恢复到初始状态。虽然数据库事务有其自身的机制,但在应用层处理某些业务逻辑的原子性时,备忘录提供了一种思路。

此外,在A/B测试或配置管理中,有时也需要保存和恢复不同的配置状态。比如,一个复杂的系统有多种运行配置,你可以在运行时切换到某个配置,然后又快速切换回另一个配置。每种配置都可以被视为一个备忘录。

如何实现一个简单的备忘录模式?

我们以一个简单的文本编辑器为例,看看如何用代码实现备忘录模式。这里用Python风格的伪代码来展示,因为它足够简洁明了。

# 1. 备忘录 (Memento)class EditorMemento:    def __init__(self, content):        self._content = content    def get_saved_content(self):        return self._content# 2. 发起人 (Originator) - 文本编辑器本身class TextEditor:    def __init__(self):        self._content = ""    def type(self, text):        self._content += text        print(f"当前内容: {self._content}")    def save(self):        # 创建一个备忘录,保存当前内容        print("保存当前内容...")        return EditorMemento(self._content)    def restore(self, memento):        # 从备忘录中恢复内容        self._content = memento.get_saved_content()        print(f"内容已恢复为: {self._content}")# 3. 负责人 (Caretaker) - 历史管理器class HistoryManager:    def __init__(self):        self._history = [] # 存储备忘录的列表    def add_memento(self, memento):        self._history.append(memento)        print("备忘录已添加到历史记录。")    def get_last_memento(self):        if not self._history:            return None        # 取出并移除最后一个备忘录(模拟撤销)        return self._history.pop()# 客户端代码if __name__ == "__main__":    editor = TextEditor()    history = HistoryManager()    # 1. 初始状态    editor.type("Hello, ")    history.add_memento(editor.save()) # 保存状态1    # 2. 修改状态    editor.type("world!")    history.add_memento(editor.save()) # 保存状态2    # 3. 继续修改    editor.type(" How are you?")    # 4. 撤销一次    print("n执行撤销操作...")    last_memento = history.get_last_memento()    if last_memento:        editor.restore(last_memento)    else:        print("没有可撤销的历史记录了。")    # 5. 再撤销一次    print("n再次执行撤销操作...")    last_memento = history.get_last_memento()    if last_memento:        editor.restore(last_memento)    else:        print("没有可撤销的历史记录了。")    # 6. 尝试再次撤销(此时没有历史了)    print("n尝试再次撤销操作...")    last_memento = history.get_last_memento()    if last_memento:        editor.restore(last_memento)    else:        print("没有可撤销的历史记录了。")

在这个例子中:

EditorMemento

是备忘录,它简单地封装了文本内容。

TextEditor

是发起人,它有

_content

状态,并提供了

save()

来创建备忘录,

restore()

来从备忘录恢复。

HistoryManager

是负责人,它维护一个备忘录列表,负责添加和获取备忘录。它完全不关心备忘录内部是什么,只知道这是一个可以存储和取出的对象。

通过这种方式,

HistoryManager

可以管理

TextEditor

的多个历史状态,而无需知道

TextEditor

内部是如何存储文本的,完美地实现了封装和解耦。这就是备忘录模式的魅力所在。

以上就是什么是备忘录模式?备忘录的应用的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1517736.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月20日 10:58:50
下一篇 2025年12月20日 10:58:57

相关推荐

  • js如何实现数组过滤

    在javascript中筛选数组元素最直接常用的方法是使用filter(),它通过回调函数对每个元素进行条件判断,返回一个由符合条件元素组成的新数组而不改变原数组;1. filter()接收一个回调函数作为参数,该函数可接受元素、索引和原数组三个参数,通常只需使用元素参数;2. 回调函数返回true…

    2025年12月20日
    000
  • JS日期格式化怎么做

    JavaScript日期格式化首选Intl.DateTimeFormat,因其支持国际化、自定义选项丰富且性能佳;对于特殊格式需求可手动拼接,解析日期字符串时应优先使用ISO 8601标准格式以确保兼容性和时区正确性。 在JavaScript中处理日期格式化,说起来简单,但真要做到灵活且兼顾国际化,…

    2025年12月20日
    000
  • JS如何实现时间切片?任务的调度

    JavaScript时间切片通过将耗时任务拆分为小任务并交还控制权,避免主线程阻塞,提升页面响应性和渲染流畅度。 JavaScript实现时间切片的核心在于避免长时间运行的脚本阻塞主线程,从而提升用户体验。它通过将大型任务分解成多个小任务,并利用 setTimeout 、 requestAnimat…

    2025年12月20日
    000
  • js怎么检查一个对象的原型

    要检查一个对象的原型,推荐使用object.getprototypeof()。1. object.getprototypeof()是标准且安全的方法,能可靠返回对象的直接原型;2. __proto__属性虽可访问原型,但属非标准遗留特性,不推荐在生产环境中使用;3. instanceof用于判断对象…

    2025年12月20日 好文分享
    000
  • 图的定义是什么?JS如何表示图结构

    图在JavaScript中常用邻接表表示,适合稀疏图和动态操作,邻接矩阵适用于顶点固定且边密集的场景,边列表则用于特定算法;实际应用如社交网络、导航和推荐系统均依赖图结构。 图,简单来说,就是由一些“点”(我们称之为顶点或节点)和连接这些点的“线”(我们称之为边)构成的抽象结构。它最核心的作用是用来…

    2025年12月20日
    000
  • JS字符串如何分割

    js字符串分割是将一个字符串按指定规则拆分为多个小字符串并存入数组;最常用方法是split(),其语法为string.split(separator, limit),separator为分隔符(可为字符串或正则表达式),limit限制返回数组的最大长度;若省略separator,则整个字符串作为单一…

    2025年12月20日
    000
  • Neo4j查询结果到D3兼容Graph JSON的转换指南

    本教程详细阐述了如何将Neo4j的查询结果高效转换为D3等前端可视化库所需的“节点与连接”(nodes & links)图JSON格式。通过利用Neo4j的APOC插件及其apoc.export.json.data过程,开发者可以轻松地将复杂的图数据结构化为易于消费的JSON对象,从而简化N…

    2025年12月20日
    000
  • 将Neo4j查询结果转换为D3兼容的Graph JSON格式教程

    本教程旨在解决Neo4j查询结果与D3等图可视化库所需的Graph JSON格式不兼容的问题。通过利用APOC库的apoc.export.json.data过程,我们将演示如何高效地将Neo4j的节点和关系数据转换为标准的nodes和links数组结构,从而简化在Node.js应用中集成图可视化的过…

    2025年12月20日
    000
  • Svelte组件实例变量的TypeScript正确类型绑定与常见问题排查

    本文深入探讨了在Svelte中使用TypeScript时,如何正确地为组件实例变量进行类型绑定(bind:this),并针对常见的TypeScript编译错误(如“Unsafe return of an any typed value”)提供了详细的解决方案。文章强调这类问题往往并非代码逻辑错误,而…

    2025年12月20日
    000
  • 在 Angular 中基于特定条件获取唯一 ID

    本文介绍了在 Angular 应用中,如何根据 JSON 数据中嵌套对象的特定条件筛选出唯一的 ID 值。通过使用 filter 和 map 方法,可以有效地从数据集中提取所需的信息,并确保结果的唯一性。本文将提供详细的代码示例和步骤,帮助你理解和应用这些技术。 数据准备 首先,我们需要准备包含数据…

    2025年12月20日
    000
  • Angular 中基于特定条件获取唯一 ID 的方法

    本文将详细介绍如何在 Angular 中,根据给定的 JSON 数据,筛选出满足特定条件的记录,并从中提取唯一的 ID 值。正如摘要所说,我们将使用 filter 和 map 方法来实现这一目标。 数据准备 首先,假设我们有以下 JSON 数据,它代表了一组用户的信息,包含 ID、姓名和个人数据: …

    2025年12月20日
    000
  • 在 Angular 中基于特定条件获取不同的 ID

    本文将介绍如何在 Angular 中使用 JavaScript 的数组方法,从 JSON 数据集中筛选出满足特定条件的唯一 ID。主要涉及 filter 和 map 方法的结合使用,以实现数据筛选、去重和提取目标字段的功能。 数据筛选 首先,我们需要使用 filter 方法根据条件筛选出符合要求的数…

    2025年12月20日
    000
  • 使用 Angular 从 JSON 数据中提取满足特定条件的唯一 ID

    本文档介绍了如何在 Angular 应用中,从 JSON 数据集中根据指定条件(例如,bloodgroup 为 “A” 且 country 为 “IN”)筛选出唯一的 id 值。我们将使用 Angular 的 filter 和 map 方法来实现这一目…

    2025年12月20日
    000
  • 实现前端页面选项过滤功能的教程

    本文旨在指导开发者如何实现一个基于前端的选项过滤功能。我们将通过一个学校信息展示的示例,详细讲解如何使用 JavaScript 和 CSS 来动态地显示和隐藏页面元素,从而实现按类别过滤学校的功能。本文将涵盖数据结构设计、HTML 结构搭建、JavaScript 逻辑编写以及 CSS 样式设置等方面…

    2025年12月20日
    000
  • 实现页面选项过滤功能的教程

    本文档旨在指导开发者如何实现一个简单的页面选项过滤功能。通过创建动态卡片并利用 JavaScript 控制其显示与隐藏,可以根据用户选择的类别过滤页面内容。本文将详细介绍 HTML 结构、CSS 样式和 JavaScript 代码,并提供完整的示例代码和注意事项,帮助读者快速掌握该功能的实现方法。 …

    2025年12月20日
    000
  • JS调试技巧有哪些

    高效的js调试工具除console.log外,还包括浏览器devtools的断点、watch表达式、call stack、network、elements和application面板;2. 利用条件断点可精准定位特定条件下的问题,dom修改断点用于追踪元素变化,事件监听器断点可捕获事件触发,xhr/…

    2025年12月20日
    000
  • JS如何实现碰撞检测

    JS碰撞检测通过几何关系判断图形是否重叠,常用AABB、圆形检测等方法;AABB因计算简单高效,适合初步筛选碰撞,广泛用于游戏开发。 JS实现碰撞检测,本质上就是判断两个或多个图形对象在二维或三维空间中是否发生重叠。这通常通过计算它们的位置和尺寸关系来完成,最常见的方法包括轴对齐包围盒(AABB)、…

    2025年12月20日
    000
  • React Redux 中跨组件共享 API 响应数据的最佳实践

    本文旨在解决 React Redux 应用中 API 响应数据在不同组件间共享的问题。通过修正 reducer 中的状态突变、优化数据获取和分发流程,并结合 useSelector hook,实现 customerId 等关键数据在各个组件中的便捷访问。文章将提供详细的代码示例,帮助开发者构建高效、…

    2025年12月20日
    000
  • React + Redux:在组件间共享 API 响应数据的最佳实践

    本文旨在解决 React 应用中使用 Redux 管理 API 响应数据时,如何在不同组件间高效、正确地共享数据的常见问题。文章将深入探讨 Redux reducer 的正确实现方式,避免 state 突变,并提供使用 useDispatch 和 useSelector hook 获取和更新数据的最…

    2025年12月20日
    000
  • React 和 Redux 中跨组件共享 API 响应数据

    本文旨在解决 React 和 Redux 应用中,API 响应数据如何在不同组件间共享的问题。通过正确使用 Redux 的 useDispatch 和 useSelector Hook,以及避免 Reducer 中的状态突变,可以有效地将 API 数据存储在 Redux store 中,并在各个组件…

    2025年12月20日
    000

发表回复

登录后才能评论
关注微信