设计模式精讲 Day 18:备忘录模式(Memento Pattern)

【设计模式精讲 Day 18】备忘录模式(Memento Pattern)


文章内容

开篇

在“设计模式精讲”系列的第18天,我们来探讨备忘录模式(Memento Pattern)。这是一种行为型设计模式,其核心思想是在不破坏封装性的前提下,捕获并外部化一个对象的内部状态,以便在需要时恢复该对象的状态

备忘录模式广泛应用于需要支持撤销操作、快照功能、版本控制等场景中。它能够帮助开发者在不暴露对象内部结构的前提下,实现对对象状态的保存与恢复,从而提升系统的灵活性和可维护性。

本篇文章将从理论到实践全面解析备忘录模式,包括其定义、结构、适用场景、实现方式、优缺点分析,并结合真实项目案例进行深入讲解,帮助你掌握这一实用的设计模式。


模式定义

备忘录模式是一种行为型设计模式,它允许在不暴露对象内部状态的情况下,捕获并存储该对象的状态,以便后续恢复。通过这种方式,可以实现对象的回退、撤销等功能。

核心思想

  • 保存状态:在对象发生变化之前,将其状态保存下来。
  • 恢复状态:在需要的时候,将对象恢复到之前保存的状态。
  • 封装性:状态的保存和恢复过程对外部是透明的,不依赖于对象的内部实现。

模式结构

备忘录模式包含以下三个关键角色:

角色职责
Originator(发起人)负责创建自身的状态快照(备忘录),并能根据备忘录恢复自身状态。
Memento(备忘录)存储Originator的内部状态,通常是一个不可变对象。
Caretaker(管理者)负责保存备忘录,但不会修改或查看备忘录的内容。

UML类图描述(文字版):

  • Originator 类有一个方法 createMemento(),用于生成一个 Memento 对象。
  • Memento 类包含 Originator 的状态信息,提供访问器方法供 Originator 使用。
  • Caretaker 类维护一个 Memento 的集合或单个实例,并通过 setMemento() 方法将 Memento 传递给 Originator,以恢复其状态。

适用场景

备忘录模式适用于以下几种典型场景:

场景说明
撤销/重做功能在编辑器、IDE等应用中,用户执行操作后可以通过备忘录模式快速回退到之前的版本。
版本控制在文档、代码等系统中,记录不同版本的状态,便于回溯或对比。
事务处理在数据库事务中,如果事务失败,可以通过备忘录模式回滚到事务开始前的状态。
游戏存档游戏中保存玩家当前状态,便于下次继续游戏。

这些场景的核心需求是:在不暴露对象内部状态的前提下,实现状态的保存与恢复


实现方式

下面是一个完整的Java实现示例,展示如何使用备忘录模式。

1. Memento 类

public class Memento {private final String state;public Memento(String state) {this.state = state;}public String getState() {return state;}
}

2. Originator 类

public class Originator {private String state;public void setState(String state) {this.state = state;}public String getState() {return state;}public Memento createMemento() {return new Memento(this.state);}public void restoreMemento(Memento memento) {this.state = memento.getState();}
}

3. Caretaker 类

import java.util.ArrayList;
import java.util.List;public class Caretaker {private List<Memento> mementos = new ArrayList<>();public void addMemento(Memento memento) {mementos.add(memento);}public Memento getMemento(int index) {return mementos.get(index);}
}

4. 测试类

public class MementoPatternDemo {public static void main(String[] args) {Originator originator = new Originator();Caretaker caretaker = new Caretaker();originator.setState("State 1");System.out.println("Initial State: " + originator.getState());caretaker.addMemento(originator.createMemento());originator.setState("State 2");System.out.println("Current State: " + originator.getState());caretaker.addMemento(originator.createMemento());originator.setState("State 3");System.out.println("Current State: " + originator.getState());caretaker.addMemento(originator.createMemento());System.out.println("Restoring to State 2...");originator.restoreMemento(caretaker.getMemento(1));System.out.println("Current State after restore: " + originator.getState());}
}
输出结果:
Initial State: State 1
Current State: State 2
Current State: State 3
Restoring to State 2...
Current State after restore: State 2

关键设计决策说明

  • Memento 是一个不可变对象,确保状态一旦创建就不会被修改。
  • Originator 只负责状态的保存和恢复,不关心如何存储或管理备忘录。
  • Caretaker 仅负责保存和获取备忘录,不参与状态的修改,保证了封装性。

工作原理

备忘录模式的工作原理基于状态的外部化存储。当对象处于某个特定状态时,调用 createMemento() 方法,将当前状态复制为一个备忘录对象。之后,可以通过 restoreMemento() 方法将对象恢复到该状态。

这种机制避免了直接访问对象内部属性,保持了良好的封装性。同时,由于备忘录本身是只读的,也防止了外部对状态的非法修改。


优缺点分析

优点缺点
封装性好:状态保存和恢复过程对外部透明,不暴露内部结构。内存消耗大:如果对象状态复杂或频繁保存,会占用较多内存。
支持撤销操作:非常适合实现撤销、重做等功能。无法跨平台恢复:备忘录通常依赖于具体实现,难以跨语言或跨系统使用。
易于扩展:可以在不修改原有代码的基础上添加新的状态保存策略。实现成本较高:对于复杂对象,需要额外编写状态保存和恢复逻辑。

案例分析

场景:文本编辑器中的撤销功能

在开发一个简单的文本编辑器时,用户可能希望支持“撤销”操作。例如,用户输入了一段文字,然后删除了部分内容,这时他希望恢复到之前的状态。

问题
  • 如何在不暴露编辑器内部状态的前提下,实现撤销功能?
  • 如何避免每次操作都重新构建整个文本内容?
解决方案

使用备忘录模式,每次用户执行操作后,将当前文本内容保存为一个备忘录。当用户点击“撤销”时,从备忘录中恢复上一个状态。

实现代码(简化版)
class TextEditor {private String content;public void setContent(String content) {this.content = content;}public String getContent() {return content;}public Memento saveState() {return new Memento(content);}public void restoreState(Memento memento) {this.content = memento.getState();}
}class EditorCaretaker {private List<Memento> history = new ArrayList<>();public void save(TextEditor editor) {history.add(editor.saveState());}public Memento getLastState() {if (history.isEmpty()) return null;return history.remove(history.size() - 1);}
}
使用示例
public class TextEditorDemo {public static void main(String[] args) {TextEditor editor = new TextEditor();EditorCaretaker caretaker = new EditorCaretaker();editor.setContent("Hello, World!");caretaker.save(editor);editor.setContent("Hello, CSDN!");caretaker.save(editor);System.out.println("Current content: " + editor.getContent());// 撤销到最后一个状态Memento lastState = caretaker.getLastState();if (lastState != null) {editor.restoreState(lastState);System.out.println("After undo: " + editor.getContent());}}
}
输出结果:
Current content: Hello, CSDN!
After undo: Hello, World!

这个案例展示了备忘录模式在实际项目中的应用价值,特别是在需要支持撤销操作的场景中非常实用。


与其他模式的关系

备忘录模式常与以下模式配合使用:

模式说明
命令模式(Command Pattern)命令模式用于封装请求,而备忘录模式用于保存状态。两者结合可用于实现撤销操作。
迭代器模式(Iterator Pattern)迭代器模式用于遍历集合,而备忘录模式可用于保存遍历过程中的状态。
状态模式(State Pattern)状态模式用于管理对象的不同状态,而备忘录模式可用于保存当前状态。两者结合可用于实现更复杂的业务逻辑。

在某些场景下,备忘录模式还可以与原型模式结合使用,实现对象的深拷贝和状态还原。


总结

本篇文章详细讲解了备忘录模式的定义、结构、适用场景、实现方式及优缺点分析,并通过一个文本编辑器的案例展示了其实际应用场景。备忘录模式在实现撤销、版本控制、事务回滚等场景中具有重要价值。

在面向对象设计中,备忘录模式体现了封装性单一职责原则,有助于提高系统的可维护性和可扩展性。

下一篇预告

明天我们将进入“设计模式精讲”的第19天,讲解观察者模式(Observer Pattern),它是实现对象间一对多依赖关系的重要设计模式,广泛应用于事件驱动系统中。敬请期待!


文章标签

design-patterns,memento-pattern,java-design-patterns,software-architecture,java-programming


文章简述

本文是“设计模式精讲”系列的第18天,重点讲解备忘录模式(Memento Pattern)。备忘录模式是一种行为型设计模式,允许在不破坏封装性的前提下,捕获并保存对象的状态,以便在需要时恢复。文章从理论到实践,系统地介绍了该模式的定义、结构、适用场景、实现方式以及优缺点分析,并通过一个文本编辑器的案例展示了其在实际项目中的应用。此外,还讨论了备忘录模式与其他设计模式的关系,如命令模式、状态模式等。通过本文的学习,读者可以掌握如何在实际开发中灵活运用备忘录模式,提升系统的可维护性和可扩展性。


进一步学习资料

  1. Design Patterns: Elements of Reusable Object-Oriented Software
  2. Head First Design Patterns
  3. Java Design Patterns - A Tutorial
  4. Java 中的备忘录模式详解
  5. Effective Java by Joshua Bloch

核心设计思想总结

通过本篇文章,我们掌握了以下核心设计思想:

  • 封装性:备忘录模式通过将状态保存在外部对象中,避免了直接暴露对象的内部状态。
  • 状态管理:支持对象状态的保存与恢复,适用于撤销、版本控制等场景。
  • 解耦设计OriginatorCaretaker 之间职责分离,降低了耦合度。
  • 可扩展性:备忘录模式易于扩展,可以在不修改原有代码的基础上增加新的状态保存策略。

在实际项目中,你可以将备忘录模式用于实现撤销功能、游戏存档、事务回滚等场景,从而提升系统的灵活性和用户体验。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/86760.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/86760.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloud系列(35)--使用HystrixDashboard进行服务监控

前言&#xff1a;在上一节中我们使用了Hystrix进行服务熔断处理&#xff0c;至此关于Hystrix的使用到此为止&#xff0c;本节内容关注的是如何使用HystrixDashboard对调用进行监控。 1、HystrixDashboard概述 Hystrix提供的准实时的调用监控(HystrixDashboard)&#xff0c;Hys…

爬虫简单实操2——以贴吧为例爬取“某吧”前10页的网页代码

需求是将贴吧的【某个吧】里面【n页】的网页代码爬取下来&#xff0c;保存至本地 首先我们要思考这个贴吧爬虫的框架&#xff0c;要有方法可以构造url列表&#xff08;就可以一次获取多个url&#xff09;&#xff0c;能请求获取相应&#xff0c;能把html保存到本地。 import …

webpack5 css-loader 配置项中的modules

在 Webpack 的 css-loader 中&#xff0c;modules 选项是一个核心配置&#xff0c;它直接关系到 CSS 的模块化处理方式。下面从概念、原理、使用场景和实践技巧四个方面详细解析&#xff1a; 概念解析&#xff1a;CSS Modules 是什么&#xff1f; CSS Modules 是一种让 CSS 类…

springboot+Vue驾校管理系统

概述 基于springbootVue开发的驾校管理系统。该系统采用主流技术栈开发&#xff0c;功能完善&#xff0c;既包含用户端便捷的操作界面&#xff0c;又具备强大的后台管理功能。 主要内容 一、用户端功能模块 ​​核心功能导航​​&#xff1a; 首页展示驾校推荐信息及最新动态…

#华为鲲鹏#华为计算#鲲鹏开发者计划2025#

#华为鲲鹏#华为计算#鲲鹏开发者计划2025# <新版开发者计划>的内容链接&#xff1a;鲲鹏开发者计划2025-鲲鹏社区 通过学习毕昇编译器整体介绍&#xff0c;以及安装使用流程、新增特性、调优工具的使用及相关指导文件获取&#xff0c;对毕异编译器有了更深的认识。 我刚…

小程序学习笔记:声明式导航的多种玩法

在小程序开发中&#xff0c;页面导航是实现用户流畅交互体验的关键环节。今天&#xff0c;咱们就一起来深入学习小程序里通过声明式导航实现页面跳转、切换 tab 页、后退等功能的技巧&#xff0c;还会附上详细代码示例&#xff0c;让大家轻松掌握&#xff01; 一、什么是页面导…

Linux中《动/静态库原理》

目录 目标文件ELF文件ELF从形成到加载轮廓ELF形成可执行readelf命令ELF可执行文件加载 理解连接与加载静态链接ELF加载与进程地址空间虚拟地址/逻辑地址 重新理解进程虚拟地址空间 动态链接与动态库加载进程如何看到动态库进程间如何共享库的动态链接动态链接到底是如何工作的&…

Android大图加载优化:BitmapRegionDecoder深度解析与实战

在移动端开发中&#xff0c;超大图片加载一直是性能优化的难点。本文将深入剖析BitmapRegionDecoder原理&#xff0c;提供完整Kotlin实现方案&#xff0c;并分享性能调优技巧。 一、为什么需要大图加载优化&#xff1f; 典型场景&#xff1a; 医疗影像&#xff1a;2000015000…

基于ApachePOI实现高德POI分类快速导入PostgreSQL数据库实战

目录 前言 一、高德POI分类简介 1、数据表格 2、分类结构 二、从Excel导入到Postgresql 1、Excel解析流程 2、Mybatis批量导入 3、数据入库 三、总结 前言 在大数据与地理信息深度交融的当下&#xff0c;地理信息系统&#xff08;GIS&#xff09;的触角已延伸至各个领域…

如何打造Apache Top-Level开源时序数据库IoTDB

引言 数据与时间结合后&#xff0c;便拥有了生命。在金融、系统日志、工业产线和智能设备等领域&#xff0c;时序数据每毫秒都在不断产生。管理这些海量时序数据需要专业的数据库系统。时序数据库产品正逐渐受到市场的关注&#xff0c;本文将分享如何通过开源的方式&#xff0…

高并发内存池实战指南

项目源码&#xff1a;https://gitee.com/kkkred/thread-caching-malloc 目录 一、脱离new&#xff1a;高并发内存池如何替代传统动态分配 1.1 new的痛点&#xff1a;碎片、延迟与锁竞争 1.2 高并发内存池的替代方案&#xff1a;分层预分配无锁管理 二、大内存&#xff08;…

基于springboot+vue的数字科技风险报告管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat12开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;Maven3.3.9 系统展示 管理员登录 管理…

实战篇----利用 LangChain 和 BERT 用于命名实体识别-----完整代码

上一篇文章讲解了Langchain,实现一个简单的demo,结合利用 LangChain 和 BERT 用于命名实体识别。 一、命名实体识别模型训练(bert+CRF) bert作为我们的预训练模型(用于将输入文本转换为特征向量),CRF作为我们的条件随机场(将嵌入特征转为标签),既然要训练,那么我们的损失函…

现代 C++ 容器深度解析及实践

一、线性容器&#xff1a;std::array 与 std::forward_list 1. std::array&#xff1a;固定大小的高效容器 在传统 C 中&#xff0c;数组与 vector 的抉择常让人纠结&#xff1a;数组缺乏安全检查&#xff0c;vector 存在动态扩容开销。C11 引入的std::array完美平衡了两者优…

数据集|猪姿态检测PigBehaviorRecognitionDataset

数据集|猪姿态检测PigBehaviorRecognitionDataset 一、数据集介绍1.1 介绍1.2 用途1.3 数据集统计 二、样本类别介绍1. Lying&#xff08;躺卧&#xff09;2. Sleeping&#xff08;睡眠&#xff09;3. Investigating&#xff08;探索&#xff09;4. Eating&#xff08;进食&…

Vue-13-前端框架Vue之应用基础路由器的使用步骤

文章目录 1 路由和路由器2 基本切换效果2.1 App.vue(根组件)2.2 components(子组件)2.2.1 Home.vue(首页)2.2.2 News.vue(新闻)2.2.3 About.vue(关于)2.3 路由器2.3.1 router/index.ts2.3.2 main.ts2.4 效果展示2.5 程序流程3 笔记3.1 路由组件和一般组件3.1.1 Header.vue(一般…

GaussDB实例级自动备份策略:构建数据安全的“自动防护网”

GaussDB实例级自动备份策略&#xff1a;构建数据安全的“自动防护网” 在数字化转型的浪潮中&#xff0c;数据库作为企业核心数据的载体&#xff0c;其安全性与可恢复性直接关系到业务的连续性。对于分布式数据库GaussDB而言&#xff0c;实例级自动备份策略是保障数据安全的关…

推荐几本关于网络安全的书

对于网络安全从业者、相关专业学生以及对网络安全感兴趣的人士而言&#xff0c;掌握扎实的网络安全知识和技能至关重要。以下推荐的几本网络安全书籍&#xff0c;涵盖了网络安全领域的多个重要方面&#xff0c;是学习和研究网络安全的优质参考资料。 1、攻击网络协议&#xff…

工业4.0浪潮下PROFIBUS DP转ETHERNET/IP在轧钢厂的创新实践

在工业自动化4.0推动制造业向智能化升级的背景下&#xff0c;轧钢厂生产对设备互联与数据协同提出更高要求。PROFIBUS DP与ETHERNET/IP协议的特性差异&#xff0c;制约着西门子PLC与工业测距仪等设备的高效协作。通过协议转换技术实现两者互通&#xff0c;为轧钢生产线注入智能…

从0开始学习R语言--Day31--概率图模型

在探究变量之间的相关性时&#xff0c;由于并不是每次分析数据时所用的样本集都能囊括所有的情况&#xff0c;所以单纯从样本集去下判断会有武断的嫌疑&#xff1b;同样的&#xff0c;我们有时候也想要在数据样本不够全面时就能对结果有个大概的了解。 例如医生在给患者做诊断…