[设计模式] 设计模式课程(十六)-- 备忘录模式(Memento)
概述
- 也叫快照(SnapShot)
- 属于行为类设计模式
- 允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态
- 软件构建过程中,某些对象的状态在转换过程中,可能由于某种需要,要求程序能回溯到对象之前处于某个点时的状态,如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现
- 如何实现对象状态的良好保存与恢复,但同时又不会因此而破坏对象本身的封装性
- 在不破坏封装性的前提下,捕捉一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态
- 备忘录(Memento)存储原发器(Originator)对象的内部状态,在需要时恢复原发器状态
- Memento模式的核心是信息隐藏,即Originator需要向外接隐藏信息,保持其封装性,但同时又需要将状态保持到外界(Memento)
- 由于现代语言(Java,C#)运行时,都具有相当的序列化支持,因此往往用效率较高,又较容易正确实现的序列化方案来实现Memento模式
结构1
- 原发器类:用于获取状态,生成备忘录对象(快照),并在需要时通过快照恢复状态
- 备忘录类:用于保存状态,通常将备忘录设为不可变的,同通过构造函数一次性传递数据
- 负责人类:仅知道“何时”及“为何”捕捉原发器的状态,以及何时恢复状态,通过备忘录栈记录原发器的历史状态,当原发器要追溯历史状态时,负责人获取栈顶备忘录,传递给原发器的恢复方法
- 备忘录类嵌套在原发器类中,原发器就可以访问备忘录的成员变量和方法
结构2
- 允许存在多种不同类型的原发器和备忘录,每种原发器都和其响应的备忘录类进行交互
- 负责人被禁止修改在备忘录中的状态,恢复方法被定义在了备忘录类中
- 每个备忘录与创建了自身的原发器连接,原发器将自己及状态传递给备忘录的构造函数
场景
- 开发一款文字编辑器应用,让用户可以撤销施加在文本上的任何操作。可让程序在执行前记录所有的对象状态,并将其保存下来,当用户以后撤销某个操作时,程序将从历史记录中获取最近的快照,然后恢复所有对象的状态
联系
- 同时使用命令模式和备忘录模式实现撤销,命令用于对目标对象执行各种不同的操作,备忘录用于保存命令执行前对象的状态
- 同时使用备忘模式和迭代器模式获取当前迭代器的状态,并在需要时进行回滚
- 对象状态比较简单时,原型模式可以作为备忘录的一个简化版本
示例1
memento.cpp
1 class Memento
2 {
3 string state;
4 //..
5 public:
6 Memento(const string & s) : state(s) {}
7 string getState() const { return state; }
8 void setState(const string & s) { state = s; }
9 };
10
11 class Originator
12 {
13 string state;
14 //....
15 public:
16 Originator() {}
17 Memento createMomento() {
18 Memento m(state);
19 return m;
20 }
21 void setMomento(const Memento & m) {
22 state = m.getState();
23 }
24 };
25
26 int main()
27 {
28 Originator orginator;
29
30 //捕获对象状态,存储到备忘录
31 Memento mem = orginator.createMomento();
32
33 //... 改变orginator状态
34
35 //从备忘录中恢复
36 orginator.setMomento(memento);
37
38 }
示例2
1 // 原发器中包含了一些可能会随时间变化的重要数据。它还定义了在备忘录中保存
2 // 自身状态的方法,以及从备忘录中恢复状态的方法。
3 class Editor is
4 private field text, curX, curY, selectionWidth
5
6 method setText(text) is
7 this.text = text
8
9 method setCursor(x, y) is
10 this.curX = curX
11 this.curY = curY
12
13 method setSelectionWidth(width) is
14 this.selectionWidth = width
15
16 // 在备忘录中保存当前的状态。
17 method createSnapshot():Snapshot is
18 // 备忘录是不可变的对象;因此原发器会将自身状态作为参数传递给备忘
19 // 录的构造函数。
20 return new Snapshot(this, text, curX, curY, selectionWidth)
21
22 // 备忘录类保存有编辑器的过往状态。
23 class Snapshot is
24 private field editor: Editor
25 private field text, curX, curY, selectionWidth
26
27 constructor Snapshot(editor, text, curX, curY, selectionWidth) is
28 this.editor = editor
29 this.text = text
30 this.curX = curX
31 this.curY = curY
32 this.selectionWidth = selectionWidth
33
34 // 在某一时刻,编辑器之前的状态可以使用备忘录对象来恢复。
35 method restore() is
36 editor.setText(text)
37 editor.setCursor(curX, curY)
38 editor.setSelectionWidth(selectionWidth)
39
40 // 命令对象可作为负责人。在这种情况下,命令会在修改原发器状态之前获取一个
41 // 备忘录。当需要撤销时,它会从备忘录中恢复原发器的状态。
42 class Command is
43 private field backup: Snapshot
44
45 method makeBackup() is
46 backup = editor.createSnapshot()
47
48 method undo() is
49 if (backup != null)
50 backup.restore()
51 // ...
示例3
1 #include <string>
2 #include <vector>
3 #include <iostream>
4 #include <ctime>
5 using namespace std;
6
7 class Memento{
8 public:
9 virtual string GetName() const = 0;
10 virtual string date() const = 0;
11 virtual string state() const = 0;
12 };
13
14 class ConcreteMemento:public Memento{
15 private:
16 string state_;
17 string date_;
18 public:
19 ConcreteMemento(string state):state_(state){
20 this->state_ = state;
21 time_t now = time(0);
22 this->date_ = ctime(&now);
23 }
24 string state() const override{
25 return this->state_;
26 }
27 string GetName() const override{
28 return this->date_ + "/(" + this->state_.substr(0,9)+"...)";
29 }
30 string date() const override{
31 return this->date_;
32 }
33 };
34
35 class Originator{
36 private:
37 string state_;
38 string GenereateRandomString(int length = 10){
39 const char alphanum[] =
40 "0123456789"
41 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
42 "abcdefghijklmnopqrstuvwxyz";
43 int stringLength = sizeof(alphanum) - 1;
44
45 string random_string;
46 for(int i = 0; i < length; i ++){
47 random_string += alphanum[rand()%stringLength];
48 }
49 return random_string;
50 }
51 public:
52 Originator(string state):state_(state){
53 cout << "Originator: My initial state is: " << this->state_ << "\n";
54 }
55
56 void DoSomething(){
57 cout << "Originator: I'm doing something important.\n";
58 this->state_ = this->GenereateRandomString(30);
59 cout << "Originator: and my state has changed to: " << this->state_ << "\n";
60 }
61
62 Memento *Save(){
63 return new ConcreteMemento(this->state_);
64 }
65
66 void Restore(Memento *memento){
67 this->state_ = memento->state();
68 cout << "Originator: My state has changed to: " << this->state_ << "\n";
69 }
70 };
71
72 class Caretaker{
73 private:
74 vector<Memento *> mementos_;
75 Originator *originator_;
76 public:
77 Caretaker(Originator *originator):originator_(originator){
78 this->originator_ = originator;
79 }
80
81 void Backup(){
82 cout << "\nCaretaker: Saving Originator's state...\n";
83 this->mementos_.push_back(this->originator_->Save());
84 }
85 void Undo(){
86 if(!this->mementos_.size()){
87 return;
88 }
89 Memento *memento = this->mementos_.back();
90 this->mementos_.pop_back();
91 cout << "Caretaker: Restoring state to: " << memento->GetName() << "\n";
92 try{
93 this->originator_->Restore(memento);
94 }catch(...){
95 this->Undo();
96 }
97 }
98 void ShowHistory() const{
99 cout << "Caretaker: Here's the list of mementos:\n";
100 for(Memento *memento : this->mementos_){
101 cout << memento->GetName() << "\n";
102 }
103 }
104 };
105
106 void ClientCode(){
107 Originator *originator = new Originator("Super-duper-super-puper-super.");
108 Caretaker *caretaker = new Caretaker(originator);
109 caretaker->Backup();
110 originator->DoSomething();
111 caretaker->Backup();
112 originator->DoSomething();
113 caretaker->Backup();
114 originator->DoSomething();
115 cout << "\n";
116 caretaker->ShowHistory();
117 cout << "\nClient: Now, let's rollback!\n\n";
118 caretaker->Undo();
119 cout << "\nClient: Once more!\n\n";
120 caretaker->Undo();
121 delete originator;
122 delete caretaker;
123 }
124
125 int main(){
126 srand(static_cast<unsigned int>(time(NULL)));
127 ClientCode();
128 return 0;
129 }
参考
https://refactoringguru.cn/design-patterns/memento
[设计模式] 设计模式课程(十六)-- 备忘录模式(Memento)的更多相关文章
- C#设计模式之二十二备忘录模式(Memento Pattern)【行为型】
一.引言 今天我们开始讲“行为型”设计模式的第十个模式,该模式是[备忘录模式],英文名称是:Memento Pattern.按老规矩,先从名称上来看看这个模式,个人的最初理解就是对某个对象的状态进行保 ...
- C#设计模式之二十二备忘录模式(Memeto Pattern)【行为型】
一.引言 今天我们开始讲"行为型"设计模式的第十个模式,该模式是[备忘录模式],英文名称是:Memento Pattern.按老规矩,先从名称上来看看这个模式,个人的最初理解就 ...
- javascript设计模式学习之十六——状态模式
一.状态模式的定义 状态模式的关键是区分事务内部和外部的状态,事务内部状态改变往往会带来事务的行为改变. 状态模式中有意思的一点是,一般我们谈到封装,都是优先封装对象的行为,而非对象的状态.但在状态模 ...
- 备忘录模式 Memento 快照模式 标记Token模式 行为型 设计模式(二十二)
备忘录模式 Memento 沿着脚印,走过你来时的路,回到原点. 苦海翻起爱恨 在世间难逃避命运 相亲竟不可接近 或我应该相信是缘份 一首<一生所爱>触动了多少 ...
- Java 设计模式系列(十八)备忘录模式(Memento)
Java 设计模式系列(十八)备忘录模式(Memento) 备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是对象的行为模式.备忘录对象是一个用来存储另外一个对象内部状态 ...
- 二十四种设计模式:备忘录模式(Memento Pattern)
备忘录模式(Memento Pattern) 介绍在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可将该对象恢复到保存的状态. 示例有一个Message实体类,某 ...
- 乐在其中设计模式(C#) - 备忘录模式(Memento Pattern)
原文:乐在其中设计模式(C#) - 备忘录模式(Memento Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 备忘录模式(Memento Pattern) 作者:webabc ...
- 模板方法模式 Template method 行为型 设计模式(二十六)
模板方法模式 Template method 上图为网上百度的一份简历模板截图 相信大家都有求职的经历,那么必然需要简历,写简历的时候,很可能你会网上检索一份简历模板,使用此模板的格式,然后替换为 ...
- 设计模式之备忘录模式(Memento)
备忘录模式(Memento) 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可将该对象恢复到原先保存的状态. Originator(发起人):负责创建一个备忘录 ...
- Java进阶篇设计模式之十二 ---- 备忘录模式和状态模式
前言 在上一篇中我们学习了行为型模式的策略模式(Strategy Pattern)和模板模式(Template Pattern).本篇则来学习下行为型模式的两个模式,备忘录模式(Memento Pat ...
随机推荐
- Powerful Number 筛学习笔记
Powerful Number 筛学习笔记 用途 \(Powerful\ number\) 筛可以用来求出一类积性函数的前缀和,最快可以达到根号复杂度. 实现 \(Powerful\ number\) ...
- mysql中FIND_IN_SET函数用法
本篇文章主要介绍mysql中FIND_IN_SET函数用法,用来精确查询字段中以逗号分隔的数据 以及其与 like 和 in 的区别 1.问题发现 之前在做工作任务时有这么一个需求:需要用接口所传的服 ...
- 学会使用 Mysql show processlist 排查问题
mysql show full processlist 查看当前线程处理情况 事发现场 每次执行看到的结果应该都有变化,因为是实时的,所以我定义为:"事发现场",每次执行就相当于现 ...
- 呵呵,Semaphore,就这?
这是并发线程工具类的第二篇文章,在第一篇中,我们分析过 CountDownLatch 的相关内容,你可以参考 一文搞懂 CountDownLatch 用法和源码! 那么本篇文章我们继续来和你聊聊并发工 ...
- JavaWeb 补充(Json)
HTML DOM alert() 方法 定义和用法 alert() 方法用于显示带有一条指定消息和一个 OK 按钮的警告框. 参数 描述 message 要在 window 上弹出的对话框中显示的纯文 ...
- 告别DNS劫持,一文读懂DoH
如果评选一个差评服务器榜单,除去育碧高居榜首外,一定也少不了 Nintendo Switch 让人头秃的联网服务.尽管任天堂已经架设了香港 CDN 服务器用于加速,但是更新安装的速度也没有什么大幅改变 ...
- JUC包的线程池详解
为什么要使用线程池 创建/销毁线程需要消耗系统资源,线程池可以复用已创建的线程. 控制并发的数量.并发数量过多,可能会导致资源消耗过多,从而造成服务器崩溃.(主要原因) 可以对线程做统一管理. JUC ...
- 基于MATLAB的手写公式识别(2)
基于MATLAB的手写公式识别 图像的预处理(除去噪声.得到后续定位分割所需的信息.) 预处理其本质就是去除不需要的噪声信息,得到后续定位分割所需要的图像信息.图像信息在采集的过程中由于天气环境的影响 ...
- [C#] NAudio 库的各种常用使用方式: 播放 录制 转码 音频可视化
概述 在 NAudio 中, 常用类型有 WaveIn, WaveOut, WaveStream, WaveFileWriter, WaveFileReader 以及接口: IWaveProvider ...
- Java基础常用类深度解析(包含常见排序算法)
目录 一.工具类 1.1.工具类的设计 1.1.1.公共静态方法 1.2.单例模式 二.包装类 2.1.基本类型的包装类 2.1.1.Integer 2.1.1.1.Integer >> ...