目录
备忘录模式的定义
备忘录模式(Memento Pattern)又叫做快照模式(Snapshot Pattern)或Token模式(Token Pattern),属于行为型设计模式。在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,当前很多软件都提供了撤销操作,其中就使用了备忘录模式。
备忘录模式的实现
备忘录模式角色
- 原发器角色(Originator):负责创建一个备忘录,记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。同时原发器还可以根据需要决定Memento存储Originator的那些内部状态。
- 备忘录角色(Memento):负责存储Originator对象的内部状态,并可以防止Originator以外的其他对象访问备忘录。备忘录有两个接口:Caretaker只能看到备忘录的窄接口,他只能将备忘录传递给其他对象。Originator却可看到备忘录的宽接口,允许它访问返回到先前状态所需要的所有数据。
- 管理者角色(Caretaker):管理者又称为负责人,它负责保存备忘录。在管理者类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象(负责任类只提供备忘录对象的读写接口,不提供备忘录属性的读写接口),也无须知道对象的实现细节。管理者对象可以保存一个备忘录数组,从而实现原发器的多次撤销。其存储这些状态信息,除了原发器外其他对象都是不可以访问的,否则就违反了封装的原则。
所以为了实现备忘录模式的封装,需要对备忘录的访问做些控制:
对原发器:可以访问备忘录里的所有信息。
对管理者:不可以访问备忘录里面的数据,但是他可以保存备忘录并且可以将备忘录传递给其他对象。
其他对象:不可访问也不可以保存,它只负责接收从负责人那里传递过来的备忘录同时恢复原发器的状态。
所以就备忘录模式而言理想的情况就是只允许生成该备忘录的那个原发器访问备忘录的内部状态。
备忘录模式类图
备忘录模式举例
要开发一个简单的草稿箱功能,假设只保留文档标题和正文,那么文档标题和正文就属于原发器角色,还需要创建一个文章备忘录,和一个草稿箱作为管理者角色,保存历史文章信息。
文章备忘录 包含了要被恢复的对象的状态。编辑器 创建并在 文章备忘录 对象中存储状态。草稿箱 对象负责从 文章备忘录 中恢复对象的状态。
备忘录模式代码实现
原发器角色
package com.common.demo.pattern.memento;
/**
* @author Evan Walker 昂焱数据: https://www.ayshuju.com
* @version 1.0
* @desc 原发器角色 - 编辑器
* @date 2023/08/05 10:26:52
*/
public class Editor {
private String title;
private String content;
public Editor() {
}
public Editor(String title, String content) {
this.title = title;
this.content = content;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public ArticleMemento saveToMemento(){
ArticleMemento articleMemento = new ArticleMemento(this.title, this.content);
return articleMemento;
}
public void undoFromMemento(ArticleMemento articleMemento){
this.title = articleMemento.getTitle();
this.content = articleMemento.getContent();
}
@Override
public String toString() {
return "Editor{" +
"title='" + title + '\'' +
", content='" + content + '\'' +
'}';
}
}
备忘录角色
package com.common.demo.pattern.memento;
/**
* @author Evan Walker 昂焱数据: https://www.ayshuju.com
* @version 1.0
* @desc 备忘录角色 - 文章
* @date 2023/08/05 10:28:21
*/
public class ArticleMemento {
// 标题
private String title;
// 正文
private String content;
public ArticleMemento() {
}
public ArticleMemento(String title, String content) {
this.title = title;
this.content = content;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
管理者角色
package com.common.demo.pattern.memento;
import java.util.Stack;
/**
* @author Evan Walker 昂焱数据: https://www.ayshuju.com
* @version 1.0
* @desc 管理员角色 - 草稿箱
* @date 2023/08/05 10:30:41
*/
public class DraftsBox {
// 存储 - 后进先出
private final Stack<ArticleMemento> STACK = new Stack<>();
public ArticleMemento getMemento() {
ArticleMemento articleMemento = STACK.pop();
return articleMemento;
}
public void addMemento(ArticleMemento articleMemento) {
STACK.push(articleMemento);
}
}
测试类
package com.common.demo.pattern.memento;
/**
* @author Evan Walker 昂焱数据: https://www.ayshuju.com
* @version 1.0
* @desc 测试类
* @date 2023/08/05 10:31:12
*/
public class Test {
public static void main(String[] args) {
DraftsBox draftsBox = new DraftsBox();
Editor editor = new Editor("初始化", "首次发表");
ArticleMemento articleMemento = editor.saveToMemento();
draftsBox.addMemento(articleMemento);
System.out.println("--------第一次编辑,保存草稿之后,信息如下:---------");
System.out.println(editor);
editor.setTitle("第一次修改");
editor.setContent("首次发表后,第一次修改");
ArticleMemento articleMemento1 = editor.saveToMemento();
draftsBox.addMemento(articleMemento1);
System.out.println("--------第一次修改,保存草稿之后,信息如下:---------");
System.out.println(editor);
editor.setTitle("第二次修改");
editor.setContent("首次发表后,第二次修改");
System.out.println("--------第一次修改,还没保存草稿,信息如下:---------");
System.out.println(editor);
System.out.println("-------撤销操作---------");
ArticleMemento memento = draftsBox.getMemento();
editor.undoFromMemento(memento);
System.out.println("--------第一次撤销,之后,信息如下:---------");
System.out.println(editor);
ArticleMemento memento2 = draftsBox.getMemento();
editor.undoFromMemento(memento2);
System.out.println("--------第二次撤销,之后,信息如下:---------");
System.out.println(editor);
}
}
测试截图
备忘录模式的特点
优点
- 提供了对象状态的存储与恢复机制:备忘录模式可以将对象的内部状态保存在备忘录中,当需要时能够比较方便地将数据恢复到某个历史的状态。
- 实现了内部状态的封装:除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
- 支持多次撤销和恢复操作:备忘录模式可以保存多个状态快照,使得客户端可以根据需求选择恢复到某个特定的状态。
缺点
- 资源消耗大:如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
- 对象状态的存储和恢复需要时间和计算资源:备忘录模式需要对对象状态进行序列化和反序列化操作,可能需要消耗一定的时间和计算资源。
使用场景
- 需要在不破坏对象封装性的前提下保存和恢复对象的状态。
- 需要实现多级撤销和恢复操作。
- 需要保存对象历史状态以供分析和审计。
注意事项
- 注意备忘录对象的生命周期管理,避免资源泄露和内存溢出。
- 确保备忘录对象只被相关的原发器对象访问,避免违反封装原则。
- 为了节约内存,可使用原型模式+备忘录模式。
实际应用
- 文本编辑器:可以使用备忘录模式来实现文本编辑器的撤销和恢复功能,将文本的不同版本保存在备忘录中。
- 游戏进度保存与加载:游戏中可以使用备忘录模式来保存和加载游戏的进度,使得玩家可以随时恢复到之前的某个状态。
- 软件撤销和恢复功能:一些软件工具或者编辑器可以使用备忘录模式来实现撤销和恢复功能,保存用户对文件的修改历史。
更多消息资讯,请访问昂焱数据(https://www.ayshuju.com)