备忘录模式是设计模式里面的对象行为型模式。他的意图是在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
有时有必要记录一个对象的内部状态。为了允许用户取消不确定的操作或从错误中恢复过来,需要实现检查点和取消机制 , 而要实现这些机制,你必须事先将状态信息保存在某处,这样才能将对象恢复到它们先前的状态。但是对象通常封装了其部分或所有的状态信息 , 使得其状态不能被其他对象访问,也就不可能在该对象之外保存其状态。而暴露其内部状态又将违反封装的原则,可能有损应用的可靠性和可扩展性。
备忘录模式的使用场景是什么样的了?
1. 必须保存一个对象在某一个时刻的 (部分)状态, 这样以后需要时它才能恢复到先前的状态。
2. 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。
下面是备忘录模式的结构图:
对上面结构做一个比较详细的说明,争取做到通俗易懂。
上图中Originate就是发起者,这里有发起者1和2,意思就是说,这个大的项目里面有不同的开发人员;发起者就要发起保存状态或者进度的要求;
MementoCaretaker就是我们要设计的一个相当于进度的或者状态保存的管理者,这个管理负责有哪两个功能了?一个就是获取到备忘录,获取这个备忘录那里来,就是发起者自己构造了一个备忘录,让MementoCaretaker来保存,同样的发起者也可以在MementoCaretaker获取他自己的备忘录。同理,第二个,第三个。。。都可以这样产生自己的备忘录。而至于这个Originate2如果获取到1产生的备忘录的话,他可能解析不了,因为备忘录在设计的时候,要保证了高内聚,也就是最好是自己解析,别人看不到,这样保证安全性;
这里的MementoIF是一个接口,这个接口就是如何保证安全性,保证你这个发起者保存的和解析的都是自己的,或者说你的东西别人拿去也看不到。就是通过这样一个设计。
我们设计了MementoIF这样一个接口,MementoCaretaker只是对这个接口对象进行操作,保存也好,管理也好,或者说是提取出来也好,所以了,这个备忘录的MementoCaretaker并不管我所保存的备忘录里面到底有什么细节,到底他是hashmap的还是arraylist的,还是具体的数组,他都不管,他只是把这个接口对象来保存。具体这个接口对象实际是怎么样的,由具体的发起者来解析、保管和处理,这样了就保证了信息的安全性。
下面是备忘录模式的交互图:
有时管理者不会将备忘录返回给原发器 , 因为原发器可能根本不需要退到先前的状态。备忘录是被动的。只有创建备忘录的原发器会对它的状态进行赋值和检索。
综上所述:备忘录模式主要是用来保存进度或者状态的,比如打游戏的时候我们要保存游戏的进度。应用程序也有一些设置或配置我们要保存。那有些人会想,这不是很简单的吗?放在本地文件,把配置信息放进去,或者把进度信息放进去,或者放到本地数据库,这样也是很有道理的,应该说大部分应用程序这样做就行了。但是如果你的应用程序比较大,很多人一起开发的,那每个人写自己的保存的状态的话,效率就不高了。我们备忘录模式主要是解决项目比较大,需要统一的考虑状态的保存或者配置的保存的时候考虑这种模式。同样了,备忘录模式里面的设计思想,也是很值得学习和参考的。
按照上面的结构图讨论一个游戏进度状态的保存问题。
游戏进度保存:对象状态、场景....
接口设计
public interface MementoIF {
}
这个接口里面什么也没有,原因是什么了?因为这个接口什么都没有,所以在具体的发起者里面对他进行实例化,或者说扩展的时候,只有这个发起者才知道,当caretaker操作这个接口的时候,他就无法调用这个接口对象实例的任何信息。是一种安全的措施。这也可以说是或者说是一种管理上的措施。
MementoCaretaker类设计:
public class MementoCaretaker {
private HashMap<String, MementoIF> mementoMap;
public MementoCaretaker() {
mementoMap = new HashMap<String, MementoIF>();
}
public MementoIF retrieveMemento(String name){
return mementoMap.get(name);
}
/*
* 备忘录赋值
*/
public void saveMemento(String name, MementoIF memento){
this.mementoMap.put(name, memento);
}
}
这里我使用了HashMap来存储数据。
Originator类设计:
public class Originator {
private HashMap<String, String> state;
public Originator(){
state = new HashMap();
}
public MementoIF createMemento(){
return new Memento(state);
}
public void restoreMemento(MementoIF memento){
state = ((Memento)memento).getState();
}
public void showState(){
System.out.println("now state is :"+state.toString());
}
public void testState1(){
state.put("blood", "500");
state.put("progress", "gate1 end");
state.put("enemy", "5");
}
public void testState2(){
state.put("blood", "450");
state.put("progress", "gate2 end");
state.put("enemy", "3");
}
private class Memento implements MementoIF{
private HashMap<String, String> state;
public Memento(HashMap state) {
this.state = new HashMap(state);//我们设置的是一个引用,而不是直接复制内容(指向的是同一块内存区域)
}
private HashMap getState(){
return state;
}
private void setState(HashMap state){
this.state = state;
}
}
}
这里注意,我的做法和一般的不一样,我把Memento写在了内部,这样做的好处就是安全,这里也一定注意this.state = new HashMap(state);Originator2类设计:
public class Originator2 {
private ArrayList<String> state;
public Originator2() {
state = new ArrayList<String>();
}
public MementoIF createMemento(){
return new Memento(state);
}
public void restoreMemento(MementoIF memento){
state = ((Memento)memento).getState();
}
public void testState1(){
state = new ArrayList<String>();
state.add("blood:500");
state.add("progress:gate1 end");
state.add("enemy:5");
}
public void testState2(){
state = new ArrayList<String>();
state.add("blood:350");
state.add("progress:gate2 end");
state.add("enemy:4");
}
public void showState(){
System.out.println("now state is :"+state.toString());
}
private class Memento implements MementoIF{
private ArrayList<String> state;
public Memento(ArrayList<String> state) {
this.state = new ArrayList(state);//我们设置的是一个引用,而不是直接复制内容(指向的是同一块内存区域)
}
private ArrayList<String> getState(){
return state;
}
private void setState(ArrayList<String> state){
this.state = state;
}
}
}
主方法执行:
public class MainTest {
public static void main(String[] args) {
MementoCaretaker mMementoCaretaker = new MementoCaretaker();
Originator mOriginator = new Originator();
Originator2 mOriginator2 = new Originator2();
System.out.println("*****Originator*****");
mOriginator.testState1();
mMementoCaretaker
.saveMemento("Originator", mOriginator.createMemento());
mOriginator.showState();
mOriginator.testState2();
mOriginator.showState();
mOriginator.restoreMemento(mMementoCaretaker
.retrieveMemento("Originator"));
mOriginator.showState();
System.out.println("*****Originator 2*****");
mOriginator2.testState1();
mOriginator2.showState();
mMementoCaretaker.saveMemento("Originator2",
mOriginator2.createMemento());
mOriginator2.testState2();
mOriginator2.showState();
mOriginator2.restoreMemento(mMementoCaretaker
.retrieveMemento("Originator2"));
mOriginator2.showState();
/*System.out.println("*****Originator&&Originator 2*****");
mOriginator.restoreMemento(mMementoCaretaker
.retrieveMemento("Originator2"));
mOriginator.showState();*/
}
}
得到的结果如下:
*****Originator*****
now state is :{enemy=5, progress=gate1 end, blood=500}
now state is :{enemy=3, progress=gate2 end, blood=450}
now state is :{progress=gate1 end, enemy=5, blood=500}
*****Originator 2*****
now state is :[blood:500, progress:gate1 end, enemy:5]
now state is :[blood:350, progress:gate2 end, enemy:4]
now state is :[blood:500, progress:gate1 end, enemy:5]
备忘录模式的优缺点:
优点:
1. 状态存储在外面,不和关键对象混合在一起,这可以帮助维护内聚
2. 提供了容易实现的恢复能力
3. 保持了关键对象的数据封装
缺点:
1. 资源消耗上面备忘录对象会很昂贵
2. 存储和恢复状态的过程比较耗时
备忘录模式有以下一些效果 :
1. 保持封装边界 使用备忘录可以避免暴露一些只应由原发器管理却又必须存储在原发器之外的信息。该模式把可能很复杂的 O r i g i n a t o r内部信息对其他对象屏蔽起来 , 从而保持了封装边界。
2. 它简化了原发器 在其他的保持封装性的设计中 , Originator负责保持客户请求过的内部状态版本。这就把所有存储管理的重任交给了 O r i g i n a t o r。让客户管理它们请求的状态将会简化O r i g i n a t o r, 并且使得客户工作结束时无需通知原发器。
3. 使用备忘录可能代价很高 如果原发器在生成备忘录时必须拷贝并存储大量的信息 , 或者客户非常频繁地创建备忘录和恢复原发器状态,可能会导致非常大的开销。除非封装和恢复O r i g i n a t o r状态的开销不大 , 否则该模式可能并不合适。
4. 定义窄接口和宽接口 在一些语言中可能难以保证只有原发器可访问备忘录的状态。
5. 维护备忘录的潜在代价 管理器负责删除它所维护的备忘录。然而 , 管理器不知道备忘录中有多少个状态。因此当存储备忘录时,一个本来很小的管理器,可能会产生大量的存储开销。
上面就是备忘录模式的一点总结,写的不好的地方希望多多包涵。