「尺有所短,寸有所长;不忘初心,方得始终。」
一、备忘录模式是什么
在日常生活中,我们经常使用备忘录来记录一些比较重要的或者容易遗忘的信息,在计算机应用中,很多软件都有撤销当前操作的功能,这都是备忘录模式的一种实现。
-
【定义】
在不破坏封装性的前提下,允许在不暴露对象实现细节的情况下「保存和恢复」对象之前的状态。
-
【「特性」】
备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,属于行为型模式。
-
【「主要作用」】
二、备忘录模式的适用场景
-
【适用场景】
-
需要保存和恢复数据的相关状态场景。 -
提供一个可回滚(rollback)的操作。 -
【生活实例】
-
数据库连接的事务管理。 -
Word、记事本等回撤功能。 -
浏览器的回退 -
游戏的存档功能 -
棋类游戏中的悔棋功能
三、备忘录模式结构
备忘录模式的核心是设计「备忘录类」以及用于「管理备忘录的管理者类」。
-
「发起人(Originator)角色」:记录当前时刻的内部状态,提供创建备忘录和恢复备忘录数据的功能,允许访问返回到先前状态所需的所有数据。实现其他业务功能,它可以访问备忘录里的所有信息。 -
「备忘录(Memento)角色」:负责存储发起人的内部状态,在需要的时候「提供发起人需要的内部状态」。 -
「管理者(Caretaker)角色」:持有一个备忘录对象的引用。对备忘录进行管理、保存和提供备忘录,其不能对备忘录的内容进行访问与修改,只能将备忘录传递给其他角色。 -
「客户端(Client)角色」:负责创建发起人和管理者角色对象。

四、备忘录模式实现方式
-
创建备忘录类,声明对应每个状态成员变量的备忘录成员变量。 -
创建管理者类,持有一个备忘录的引用,当备忘的对象时多个时,持有的是一个数组对象,定义添加,获取某一个备忘录对象的方法, -
创建发起者类,定义创建备忘录,记录当前时刻自身的内部状态,通过备忘录恢复内部状态 -
客户端负责创建发起人和管理者角色对象。
五、备忘录模式的实现
【案例】:下象棋游戏中的悔棋功能
【案例说明】:下象棋的游戏中都有悔棋的功能,这个功能中记录每走一步棋所在位置的状态是用备忘录角色,移动棋子的时发起人角色,管理者负责往备忘录角色中添加棋子的轨迹
-
「发起人(Originator)角色」
/**
* @describe 发起人(Originator)角色
* @author Edwin
* @date 2021/11/18 19:46
*/
public class Originator {
/**
* 当前棋子名字
*/
public String pieceName;
/**
* 当前棋子位置
*/
public String location;
/**
* @describe 设置当前棋子的状态
* @author Edwin
* @date 2021/11/18 19:52
*/
public void setOriginatorStatus(String pieceName,String location){
this.pieceName = pieceName;
this.location = location;
}
/**
* @describe 工厂方法,返回一个备忘录对象
* @author Edwin
* @date 2021/11/18 19:49
*/
public Memento createMemento(String pieceName,String location){
return new Memento(pieceName,location);
}
/**
* @describe 将棋子的状态回滚到上一步
* @author Edwin
* @date 2021/11/18 19:56
*/
public void rollback(Memento memento){
setOriginatorStatus(memento.getPieceName(),memento.getLocation());
}
} -
「备忘录(Memento)角色」
/**
* 备忘录(Memento)角色 :棋子轨迹
*/
@Data
public class Memento {
/**
* 棋子名字
*/
public String pieceName;
/**
* 棋子位置
*/
public String location;
public Memento(String pieceName, String location) {
this.pieceName = pieceName;
this.location = location;
}
} -
「管理者(Caretaker)角色」
/**
* @describe 管理者(Caretaker)角色
* @author Edwin
* @date 2021/11/18 19:43
*/
public class Caretaker {
/**
* 持有一组备忘录对象的引用 这里有多个棋子轨迹的备忘录,
* 使用Java栈(先进后出)实现最后一步走得棋子,最先后悔
* @author Edwin
* @date 2021/11/18 19:41
*/
private Stack stack;
public Caretaker() {
this.stack = new Stack();
}
/**
* 往栈里面放一个Memento Memento 记录着每一步的棋子的轨迹
* @author Edwin
* @date 2021/11/18 19:41
*/
public void push(Memento memento) {
stack.push(memento);
}
/**
* 从栈里面取出一个Memento 悔棋
* @author Edwin
* @date 2021/11/18 19:40
*/
public Memento pop() {
if (stack.empty()) {
//栈空了!已经是第一步了,没办法悔棋了!
return null;
}
return (Memento)stack.pop();
}
} -
「客户端代码实现」
public class MementoMain {
public static void main(String[] args) {
//负责创建发起人和管理者角色对象。
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
// 设置棋子初始位置
originator.setOriginatorStatus("车","K1");
System.out.println("棋子初始位置:棋子:" + originator.pieceName + "的位置是:" + originator.location);
// 备份棋子初始位置信息
caretaker.push(originator.createMemento(originator.pieceName,originator.location));
// 走第一步棋
originator.setOriginatorStatus("车","K10");
System.out.println("第一步:棋子:" + originator.pieceName + "的位置是:" + originator.location);
// 备份第一步的棋子信息
caretaker.push(originator.createMemento(originator.pieceName,originator.location));
// 走第二步棋
originator.setOriginatorStatus("车","K20");
System.out.println("第二步:棋子:" + originator.pieceName + "的位置是:" + originator.location);
// 备份第二步的棋子信息
caretaker.push(originator.createMemento(originator.pieceName,originator.location));
// 走第三步棋
originator.setOriginatorStatus("车","K30");
System.out.println("第三步:棋子:" + originator.pieceName + "的位置是:" + originator.location);
//开始悔棋
for (int i = 1; i < 1000; i++) {
//模拟一直悔棋,知道不能反悔为止
Memento pop = caretaker.pop();
if(pop != null){
originator.setOriginatorStatus(pop.getPieceName(),pop.getLocation());
System.out.println("第" + i + "次悔棋后:棋子:" + originator.pieceName + "的位置是:" + originator.location);
}else {
System.out.println("第" + i + "次悔棋后:现在已经是第一步,没有棋可以悔了,当前棋子:" + originator.pieceName + "的位置是:" + originator.location);
break;
}
}
}
} -
「案例输出结果」

六、备忘录模式的优缺点
-
「优点」
-
提供了一种可以恢复状态的机制。可以将数据恢复到用户需要的某个历史的状态。 -
备忘录模式复杂的发起人内部信息对其他的对象屏蔽起来,实现了内部状态的封装。 -
符合「单一职责原则」,发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理。 -
「缺点」
当保存的内部状态信息过多或者特别频繁时,内存资源消耗大。
七、备忘录模式和其他模式的联系
-
可以同时使用【命令模式】和【备忘录模式】来实现「撤销」。命令用于对目标对象执行各种不同的操作, 备忘录用来保存一条命令执行前该对象的状态。
-
可以同时使用【备忘录模式】和【迭代器模式】来获取当前迭代器的状态, 并且在需要的时候进行回滚。
-
【原型模式】可以作为【备忘录】的一个简化版本,在备忘录模式中,通过定义「备忘录」来备份「发起人」的信息, 而原型模式的 clone() 方法具有自备份功能,只需要发起人实现 Cloneable 接口就有备份自己的功能,此时可以删除备忘录类。

八、总结
8.1 备忘录模式两个概念
-
「窄接口」
只允许它把备忘录对象传给其他的对象。针对的是负责人对象和其他除发起人对象之外的任何对象。
-
「宽接口」
允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。针对发起人。
8.2 备忘录模式四种模式
-
白箱模式
将发起人角色的状态存储在一个所有对象都看得到的地方,备忘录内部所存储的状态对所有对象公开。
-
黑箱模式
备忘录角色对发起人角色对象提供一个宽接口,而为其他对象提供一个窄接口。简单说就是将备忘录定义为一个接口,实现类放到发起人里面,外界不可见。
-
多重检查点
常见的系统往往需要存储不止一个状态,而是需要存储多个状态,或者叫做有多个检查点。用list或者map存储,这种叫做多重检查点
-
自述历史模式
发起人角色自己兼任管理者角色。将管理者的行为在发起人中实现,通过发起人自行维护历史状态。
原文始发于微信公众号(星河之码):设计模式(20):备忘录模式
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/27093.html