1 概述
1.1 引言
在手机上玩象棋时,往往会提供一个悔棋的功能,实际上,悔棋就是恢复到某个历史状态,很多软件中称之为撤销,实现撤销时,需要先保存历史状态,这样撤销时,取出某个历史状态并覆盖当前状态。备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态恢复。
1.2 定义
备忘录模式:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
备忘录模式是一种对象行为型模式,别名为Token。
1.3 结构图
1.4 角色
Originator
(原发器):一个普通类,可以创建一个备忘录,并存储当前内部状态,也可以使用备忘录恢复内部状态,一般为需要保存内部状态的类Memento
(备忘录):存储原发器内部状态,根据原发器决定保存哪些内部状态。除了原发器以及负责人以外,备忘录不能供其他对象直接使用Caretaker
(负责人):又叫管理者,负责保存备忘录,可以存储一个或多个备忘录,只负责保存备忘录对象,不能修改备忘录
2 典型实现
2.1 步骤
- 定义原发器:原发器是需要保存内部状态的类,提供一个从当前状态创建备忘录的方法以及一个从备忘录中恢复内部状态的方法
- 定义备忘录:存储原发器内部状态,需要考虑封装性,不能被除原发器以及负责人以外的类访问,否则失去备忘录意义
- 定义负责人:保存备忘录对象,一般使用集合存储多个备忘录
2.2 原发器
class Originator
{
private String state;
public String getState()
{
return this.state;
}
public void setState(String state)
{
this.state = state;
}
public Memento save()
{
return new Memento(state);
}
public void restore(Memento memento)
{
this.state = memento.getState();
}
}
原发器中相应的字段表示内部状态,save()
返回一个将内部状态封装为备忘录的对象,restore()
获取备忘录中的内部状态并进行恢复。
2.3 备忘录
class Memento
{
private String state;
public Memento(String state)
{
this.state = state;
}
public String getState()
{
return this.state;
}
}
简单的保存内部状态的类,需要保证封装性,不允许除原发器以及负责人外的类访问。
不同语言实现机制不同,比如C++中可以通过friend
友元实现,Java中可通过将备忘录或者原发器防置同一个包或者将备忘录作为原发器的内部类实现。
2.4 负责人
class Caretaker
{
private List<Memento> mementos = new LinkedList<>();
public Memento get()
{
return mementos.remove(0);
}
public void add(Memento memento)
{
mementos.add(0,memento);
}
}
负责人使用一个LinkedList
保存多个备忘录,由于恢复操作是逐步进行的,也就是不能一次恢复到“撤销两次”的历史状态,只能恢复到“撤销一次”的历史状态,因此可以考虑栈来保存备忘录。
2.5 客户端
public static void main(String[] args)
{
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
originator.setState("状态1");
caretaker.add(originator.save());
originator.setState("状态2");
caretaker.add(originator.save());
originator.setState("状态3");
caretaker.add(originator.save());
originator.restore(caretaker.get());
System.out.println(originator.getState());
originator.restore(caretaker.get());
System.out.println(originator.getState());
originator.restore(caretaker.get());
System.out.println(originator.getState());
}
对于客户端,每修改一次原发器的状态就通过负责人保存一次生成的备忘录,需要时从负责人获取备忘录并恢复到相应的状态。
输出:
3 实例
象棋悔棋的实现,使用备忘录模式进行设计。
设计如下:
- 原发器:
Chessman
- 备忘录:
Memento
- 负责人:
Caretaker
原发器如下:
class Chessman
{
private String label;
private int x;
private int y;
public Chessman(int x,int y,String label)
{
this.x = x;
this.y = y;
this.label = label;
}
public Memento save()
{
return new Memento(x,y,label);
}
public void restore(Memento memento)
{
this.x = memento.getX();
this.y = memento.getY();
this.label = memento.getLabel();
}
//setter+getter...
@Override
public String toString()
{
return "x:"+x+" y:"+y+" label:"+label;
}
}
原发器的两个核心方法就是save()
与restore
,save()
将内部状态保存为备忘录,而restore()
根据备忘录参数恢复到之前的内部状态。
备忘录如下:
class Memento
{
private int x;
private int y;
private String label;
public Memento(int x,int y,String label)
{
this.x = x;
this.y = y;
this.label = label;
}
//getter...
}
属性与原发器一致,最后是负责人:
class Caretaker
{
private List<Memento> mementos = new LinkedList<>();
public Memento get()
{
return mementos.remove(0);
}
public void add(Memento memento)
{
mementos.add(0,memento);
}
}
使用LinkedList
模拟栈的操作,get
获取栈顶的状态。
测试:
public static void main(String[] args)
{
Chessman chessman = new Chessman(1,2,"车");
Caretaker caretaker = new Caretaker();
caretaker.add(chessman.save());
chessman.setX(8);
caretaker.add(chessman.save());
chessman.setY(5);
caretaker.add(chessman.save());
chessman.restore(caretaker.get());
System.out.println(chessman);
chessman.restore(caretaker.get());
System.out.println(chessman);
chessman.restore(caretaker.get());
System.out.println(chessman);
}
输出:
4 备忘录封装
备忘录是一个特殊的对象,只有原发器对它拥有控制权力,负责人只负责管理备忘录,其他类无法直接访问备忘录,因此需要对备忘录进行封装。在Java中可以使用内部类对备忘录进行封装,比如上面的例子可以封装内部类如下:
class Chessman
{
//...
public class Memento
{
private int x;
private int y;
private String label;
public Memento(int x,int y,String label)
{
this.x = x;
this.y = y;
this.label = label;
}
public int getX()
{
return this.x;
}
public int getY()
{
return this.y;
}
public String getLabel()
{
return this.label;
}
}
//...
}
这样可以最大程度地限制外部类对于备忘录的访问,如果想进一步的完全限制,可以将备忘录设置为私有内部类,将负责人类也作为原发器的内部类,这样外部类就完全不能访问备忘录:
class Chessman
{
//...
private class Memento
{
//...
}
public class Caretaker
{
private List<Memento> mementos = new LinkedList<>();
public Memento get()
{
return mementos.remove(0);
}
public void add(Memento memento)
{
mementos.add(0,memento);
}
}
public Caretaker getCaretaker()
{
return new Caretaker();
}
}
5 主要优点
- 状态恢复:备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史状态
- 多次撤销:备忘录实现了对信息的封装,保存了原发器的状态,配合列表,堆栈等集合可以实现多次撤销操作
6 主要缺点
- 资源消耗大:如果需要保存的原发器状态太多,将会占用大量的存储空间,每保存一次对象的状态都需要消耗一定的系统资源
7 适用场景
- 保存一个对象在某一个时刻的全部状态或部分状态,这样以后需要时就能恢复到先前的状态,实现撤销操作
- 防止外界对象破坏一个对象历史状态的封装性,避免将历史状态的实现细节暴露给外部对象
8 总结
如果觉得文章好看,欢迎点赞。
同时欢迎关注微信公众号:氷泠之路。