备忘录模式——撤销功能的实现
1、简介
1.1、概述
备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤。当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。当前很多软件都提供了撤销(Undo)操作,其中就使用了备忘录模式。
1.2、定义
备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它是一种对象行为型模式,其别名为Token。
2、解析
2.1、UML类图
备忘录模式的核心是备忘录类以及用于管理备忘录的负责人类的设计,其结构如下图所示:
可以看出,在备忘录模式结构图中包含以下3个角色:
- Originator(原发器):它是一个普通类,可以创建一个备忘录,并存储其当前内部状态,也可以使用备忘录来恢复其内部状态。一般将需要保存内部状态的类设计为原发器。
- Memento(备忘录):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用。原发器的设计在不同的编程语言中实现机制会有所不同。
- Caretaker(负责人):负责人又称为管理者,他负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在负责人类中可以存储一个或多个备忘录对象,他只负责存储对象,而不能修改对象,也无须知道对象的实现细节。
2.2、代码示例
理解备忘录模式并不难,但关键在于如何设计备忘录类和负责人类。由于在备忘录中存储的是原发器的中间状态,因此需要防止原发器以外的其他对象访问备忘录,特别是不允许其他对象来修改备忘录。下面通过简单的示例代码来说明如何使用Java语言实现备忘录模式。
在使用备忘录模式时,首先应该存在一个原发器类Originator。在真实业务中,原发器类是一个具体的业务类,它包含一些用于存储成员数据的属性,典型代码如下:
/*** @Description: 原发器类* @Author: yangyongbing* @CreateTime: 2023/08/03* @Version: 1.0*/
public class Originator {private String state;public Originator() {}// 创建一个备忘录对象public Memento createMemento(Memento memento){return new Memento(this);}// 根据备忘录对象恢复原发器状态public void restoreMemento(Memento memento){state=memento.getState();}public String getState() {return state;}public void setState(String state) {this.state = state;}
}
对于备忘录类Memento而言,它通常提供了与原发器相对应的属性(可以是全部,也可以是部分)用于存储原发器的状态。典型的备忘录类设计代码如下:
/*** @Description: 备忘录类,默认可见性,包内可见* @Author: yangyongbing* @CreateTime: 2023/08/03 12:55* @Version: 1.0*/
class Memento {private String state;public Memento(Originator originator) {state=originator.getState();}public String getState() {return state;}public void setState(String state) {this.state = state;}
}
在设计备忘录类时需要考虑其封装性,除了Originator类,不允许其他类来调用备忘录类Memento的构造函数与相关方法。如果不考虑封装性,允许其他类调用setState()等方法,将导致在备忘录中保存的历史状态发生改变,通过撤销操作所恢复的状态就不再是真实的历史状态,备忘录模式也就失去了本身的意义。
在使用Java语言实现备忘录模式时,一般通过将Memento类与Originator类定义在同一个包(package)中来实现封装。在Java语言中可使用默认访问标识符来定义Memento类,即保证其包内可见。只有Originator类可以对Memento进行访问,而限制了其他类对Memento的访问。在Memento中保存了Originator的state值,如果Originator中的state值改变之后需撤销,可以通过调用它的restoreMemento()方法进行恢复。
对于负责人类Caretaker,它用于保存备忘录对象,并提供getMemento()方法用于向客户端返回一个备忘录对象。原发器通过使用这个备忘录对象可以回到某个历史状态。典型的负责人类的实现代码如下:
/*** @Description: 负责人类* @Author: yangyongbing* @CreateTime: 2023/08/03 13:08* @Version: 1.0*/
public class Caretaker {private Memento memento;public Memento getMemento() {return memento;}public void setMemento(Memento memento) {this.memento = memento;}
}
在Caretaker类中不应该直接调用Memento中的状态改变方法,它的作用仅仅用于存储备忘录对象。将原发器备份生成的备忘录对象存储在其中,当用户需要对原发器进行恢复时再将存储在其中的备忘录对象取出。
2.3、备忘录的封装
备忘录是一个很特殊的对象,只有原发器对它拥有控制的权力,负责人只负责管理备忘录,而其他类无法直接访问到备忘录,因此需要对备忘录进行封装。
为了实现对备忘录对象的封装,需要对备忘录的调用进行控制。对于原发器而言,它可以调用备忘录的所有信息,可以访问返回到先前状态所需的所有数据。对于负责人而言,只负责备忘录的保存并将备忘录传递给其他对象。对于其他对象而言,只需要从负责人处取出备忘录对象并将原发器对象的状态恢复,而无须关心备忘录的保存细节。理想的情况是只允许生成该备忘录的那个原发器访问备忘录的内部状态。
在实际开发中,原发器与备忘录之间的关系是非常特殊的,它们要分享信息而不让其他类知道,实现方法因编程语言的不同而有所差异。在C++中可以使用friend关键字,让原发器类和备忘录类成为友元类,相互之间可以访问对方的一些私有属性。在Java语言中可以将原发器类和备忘录类放在一个包中,让它们之间满足默认的包内可见性,也可以将备忘录类作为原发器类的内部类,使得只有原发器才可以访问备忘录中的数据,其他对象都无法直接使用备忘录中的数据。
3、备忘录模式总结
备忘录模式在很多软件的使用过程中普遍存在,但是在应用软件开发中,它的使用频率并不太高,因为现在很多基于窗体和浏览器的应用软件并没有提供撤销操作。如果需要为软件提供撤销功能,备忘录模式无疑是一种很好的解决方案。在一些字处理软件、图像编辑软件、数据库管理系统等软件中备忘录模式都得到了很好的应用。
3.1、主要优点
- 它提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤。当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。
- 备忘录实现了对信息的封装。一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。备忘录保存了原发器的状态,采用列表、堆栈等集合来存储备忘录对象可以实现多次撤销操作。
3.2、主要缺点
备忘录模式的主要缺点是:资源消耗过大。如果需要保存的原发器类的成员变量太多,就不可避免地需要占用大量的存储空间,每保存一次对象的状态都需要消耗一定的系统资源。
3.3、适用场景
- 保存一个对象在某一个时刻的全部状态或部分状态,这样以后需要时就能够恢复到先前的状态,实现撤销操作。
- 防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象。
相关文章:

备忘录模式——撤销功能的实现
1、简介 1.1、概述 备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤。当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。当前很多软件都提供了撤销(Undo)操作…...

Golang 函数参数的传递方式 值传递,引用传递
基本介绍 我们在讲解函数注意事项和使用细节时,已经讲过值类型和引用类型了,这里我们再系统总结一下,因为这是重难点,值类型参数默认就是值传递,而引用类型参数默认就是引用传递。 两种传递方式(函数默认都…...

K8s影响Pod调度和Deployment
5.应用升级回滚和弹性伸缩...

透明代理和不透明代理
透明代理和不透明代理 1、透明代理(Transparent Proxy)2、不透明代理(Non-Transparent Proxy)3、工作原理4、透明代理为啥比不透明代理多一部先连接到路由再到代理服务器?5、这里路由器做了什么工作6、代理自动配置文件(Proxy Auto-Configuration file,PAC file)7、代理…...

1424. 对角线遍历 II;2369. 检查数组是否存在有效划分;1129. 颜色交替的最短路径
1424. 对角线遍历 II 核心思想:我感觉是一个技巧题,如果想到很容易做出了,想不到就很难了。首先对于一条对角线的数,其坐标ij是一样的,然后同一条对角线斜向上的j是从小到大的,知道这个就很容易做出来了。…...

【漏洞复现】Metabase 远程命令执行漏洞(CVE-2023-38646)
文章目录 前言声明一、漏洞介绍二、影响版本三、漏洞原理四、漏洞复现五、修复建议 前言 Metabase 0.46.6.1之前版本和Metabase Enterprise 1.46.6.1之前版本存在安全漏洞,未经身份认证的远程攻击者利用该漏洞可以在服务器上以运行 Metabase 服务器的权限执行任意命…...

Linux 9的repo for OVS build
源码中自带RPM包spec文件 cd /root/rpmbuild/SOURCES/openvswitch-2.17.7/rhel rpmbuild -bb openvswitch.spec ## 按提示解决,不好解决的依赖可以试试下面的repo 方法 error: File /root/rpmbuild/SOURCES/openvswitch-2.17.7.tar.gz: No such file or direct…...

DOCTYPE 是什么作用?
聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ DOCTYPE 是什么作用?⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅!这个专栏是为那些对Web开发感兴…...

KubeSphere 3.4.0 发布:支持 K8s v1.26
2023 年 07 月 26 日,KubeSphere 开源社区激动地向大家宣布,KubeSphere 3.4.0 正式发布! 让我们先简单回顾下之前三个大版本的主要变化: KubeSphere 3.1.0 新增了“边缘计算”、“计量计费” 等功能,将 Kubernetes 从…...

自然语言文本分类模型代码
以下是一个基于PyTorch的文本分类模型的示例代码,用于将给定的文本分为多个预定义类别: import torch import torch.nn as nn import torch.nn.functional as Fclass TextClassifier(nn.Module):def __init__(self, vocab_size, embedding_dim, hidden_…...

Prometheus实现系统监控报警邮件
Prometheus实现系统监控报警邮件 简介 Prometheus将数据采集和报警分成了两个模块。报警规则配置在Prometheus Servers上, 然后发送报警信息到AlertManger,然后我们的AlertManager就来管理这些报警信息,聚合报警信息过后通过email、PagerDu…...

could not import go.etcd.io/etcd/clientv3-go
问题描述 今天在封装etcd的时候导包报错: could not import go.etcd.io/etcd/clientv3 (no required module provides package "go.etcd.io/etcd/clientv3") 问题解决: get:确保下载了client包 go get go.etcd.io/etcd/client tidy go mod tidy 本文由 mdnice 多平台…...

MySQL的行锁、表锁触发
MySQL的行锁、表锁触发 sql CREATE TABLE products ( product_id INT PRIMARY KEY, product_name VARCHAR(50), stock INT ); INSERT INTO products (product_id, product_name, stock) VALUES (1001, ‘商品A’, 50), (1002, ‘商品B’, 30), (1003, ‘商品C’, 20); 一、行锁…...

mysql-入门笔记-3
# ----------排序查询-------- # 语法 # select 字段列表 from 表名 order by 字段1 排序方式1 ,字段2 排序方式2 ; DESC 降序 ASC升序 # 1 根据年龄对公司的员工进行升序排序---默认升序-黄色提示代码冗余 select * from userTable order by age ASC ; # 2 根据入职时间,对员…...

3分钟创建超实用的中小学新生录取查询系统,现在可以实现了
在新学期开始之际,作为招生负责人,您是否已经做好准备来迎接新学年的招生工作呢?录取新生所需的任务包括录入成绩信息、核对招生要求以及公布新生录取信息等,这些工作繁重而具有挑战性,给负责招生的老师带来了巨大的压…...

Redis 变慢了 解决方案
一、Redis为什么变慢了 1.Redis真的变慢了吗? 对 Redis 进行基准性能测试 例如,我的机器配置比较低,当延迟为 2ms 时,我就认为 Redis 变慢了,但是如果你的硬件配置比较高,那么在你的运行环境下ÿ…...

远程仓库的操作
一、远程仓库的操作命令 git remote # 查看当前项目关联的远程库 我事先关联了一个GitHub的远程仓库,关于如何关联远程仓库,可以看这篇文章远程仓库GitHub和Gitee_林涧泣的博客-CSDN博客 git remote add [仓库服务器名] [远程仓库地址] # 关联远程仓库…...

一个监控系统的典型架构
监控系统的典型架构图,从左往右看,采集器是负责采集监控数据的,采集到数据之后传输给服务端,通常是直接写入时序库。然后就是对时序库的数据进行分析和可视化,分析部分最典型的就是告警规则判断,即图上的告…...

让GPT人工智能变身常用工具-中
...

HCIP中期实验
1、该拓扑为公司网络,其中包括公司总部、公司分部以及公司骨干网,不包含运营商公网部分。 2、设备名称均使用拓扑上名称改名,并且区分大小写。 3、整张拓扑均使用私网地址进行配置。 4、整张网络中,运行OSPF协议或者BGP协议的设备…...

《向量数据库指南》——向量数据库Milvus Cloud、Pinecone、Vespa、Weaviate、Vald、GSI 、 Qdrant选哪个?
1、Milvus Cloud(https://milvuscloud.com) Milvus是一个开源的向量数据库,支持高效的向量搜索和相似度匹配。它针对大规模向量数据集的性能进行了优化,并提供了Python、Java、Go和C++等多种语言的客户端接口。Milvus在图像、音频、文本和推荐等领域都有广泛的应用。 2…...

python与深度学习(十一):CNN和猫狗大战
目录 1. 说明2. 猫狗大战2.1 导入相关库2.2 建立模型2.3 模型编译2.4 数据生成器2.5 模型训练2.6 模型保存2.7 模型训练结果的可视化 3. 猫狗大战的CNN模型可视化结果图4. 完整代码5. 猫狗大战的迁移学习 1. 说明 本篇文章是CNN的另外一个例子,猫狗大战,…...

经典CNN(三):DenseNet算法实战与解析
🍨 本文为🔗365天深度学习训练营中的学习记录博客🍖 原作者:K同学啊|接辅导、项目定制 1 前言 在计算机视觉领域,卷积神经网络(CNN)已经成为最主流的方法,比如GoogleNet,…...

学习笔记——压力测试案例,监控平台
测试案例 # 最简单的部署方式直接单机启动 nohup java -jar lesson-one-0.0.1-SNAPSHOT.jar > ./server.log 2>&1 &然后配置执行计划: 新建一个执行计划 配置请求路径 配置断言配置响应持续时间断言 然后配置一些查看结果的统计报表或者图形 然后我…...

sqlite 踩坑
内存数据库 强制SQLite数据库单纯的存在于内存中的常用方法是使用特殊文件名“ :memory: ” db QSqlDatabase::addDatabase("QSQLITE", "MEMORY"); db.setDatabaseName(":memory:"); 调用此接口完成后,不…...

【论文笔记】神经网络压缩调研
神经网络压缩调研 背景现有的深度模型压缩方法NetWork Prunning 网络剪枝设计结构化矩阵知识蒸馏权值共享Parameter Quantization(参数量化)量化和二进制化伪量化Architecture Design(Depth Separable Convolution)分解卷积 背景 …...

红外NEC通信协议
一、NEC简介 红外(Infrared,IR)遥控是一种无线、非接触控制技术,常用于遥控器、无线键盘、鼠标等设备之间的通信。IR协议的工作原理是,发送方通过红外线发送一个特定的编码,接收方通过识别该编码来执行相应的操作。 IR协议是指红外…...

数据分析DAY1
数据分析 引言 这一周:学习了python的numpy和matplotlib以及在飞桨paddle上面做了几个小项目 发现numpy和matplotlib里面有很多api,要全部记住是不可能的,也是不可能全部学完的,所以我们要知道并且熟悉一些常用的api࿰…...

算法通关村—迭代实现二叉树的前序,中序,后序遍历
1. 前序中序后序递归写法 前序 public void preorder(TreeNode root, List<Integer> res) {if (root null) {return;}res.add(root.val);preorder(root.left, res);preorder(root.right, res);}后序 public static void postOrderRecur(TreeNode head) {if (head nu…...

二叉搜索树(BST)的模拟实现
序言: 构造一棵二叉排序树的目的并不是为了排序,而是为了提高查找效率、插入和删除关键字的速度,同时二叉搜索树的这种非线性结构也有利于插入和删除的实现。 目录 (一)BST的定义 (二)二叉搜…...