当我们打游戏时,如果打boss时挂了,我们希望下次再打boss时,玩家的战斗值能恢复到打boss之前。就是说,我们希望在某一时刻,可以将游戏的各种数据保存,这样当我们需要时,可以恢复到这个状态。这里引入了一种新的模式——备忘录模式。
备忘录模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。(引自《大化设计模式》)
Originator:发起人。Originator负责创建一个备忘录Memento,用来记录当前时刻它的内部状态,并可以使用备忘录恢复内部状态。Originator根据需要决定Memento存储Originator的哪些内部状态,即哪些属性需要保存就将哪些属性存储在Memento中。
Memento:备忘录。负责存储Originator对象的内部状态,并防止Originator以外的其他对象访问备忘录。备忘录有两个接口,Caretaker只能看到备忘录的窄接口,它只能将备忘录传递给其他对象。Originator能够看到一个宽接口,允许访问它返回到先前状态所需的所有数据。
Caretaker:负责人(或称管理者)。负责保存好备忘录,不能对备忘录的内容进行操作或检查。
在备忘录的角色中,定义了它必须对不同的人提供不同的接口,对发起人提供宽接口,对其它人提供窄接口。
什么意思呢?发起人可以查看备忘录的属性,并对备忘录中的属性进行操作(这是必然的,备忘录中存储的就是发起人需要保存的属性);而负责人不可以查看或操作备忘录中的属性。
由于这个区别,备忘录可以有两种实现:
一是只提供宽接口,这种实现方式我们称之为白箱备忘录。
一是提供双接口,宽接口+窄接口,这种实现方式我们称之为黑箱备忘录。
上面已经说过,在备忘录的角色中,定义了它必须对不同的人提供不同的接口,对发起人提供宽接口,对其它人提供窄接口。如果都提供宽接口,我们称之为白箱备忘录。
类图和上面的基本类图一致,角色都是这些,具体角色这里就不再赘述。
直接上代码进行说明。
备忘录Memento
public class Memento {
//需要保存的数据属性,可以有多个
private String state;
//构造函数,将相关数据导入
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
发起人Originator
public class Originator {
//Originator的内部状态,即需要保存的属性,可以有多个
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
//创建一个备忘录,将当前需要保存的信息导入并实例化出一个Memento对象
//即Originator需要保存的信息state存储在Memento对象中
public Memento createMemento() {
return new Memento(this.state);
}
//恢复备忘录,将Memento导入,并将相关数据恢复
public void restoreMemento(Memento memento) {
this.state = memento.getState();
}
}
负责人Caretaker
public class Caretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
测试类
public class MementoTest {
public static void main(String[] args) {
Originator o = new Originator();
o.setState("On");//设置Originator的初始状态属性值为"On"
System.out.println("初始状态为:"+o.getState());//显示初始状态
//保存原始状态信息,由于封装在了Memento中,我们并不知道具体都保存了哪些属性
Caretaker c = new Caretaker();
c.setMemento(o.createMemento());
//改变Originator的状态属性值为"Off"
o.setState("Off");
System.out.println("更改状态为:"+o.getState());//显示更改后的状态
//恢复初始状态
o.restoreMemento(c.getMemento());
System.out.println("恢复初始状态为:"+o.getState());
}
}
结果
关于封装性不好,我这里说一下,这里的封装性不好是指备忘录Memento的封装性不好,它将它的方法(构造函数和getState())暴露给了发起人Originator以外的人了,安全性不够好。
在备忘录的角色中,定义了它必须对不同的人提供不同的接口,对发起人提供宽接口,对其它人提供窄接口。
如果宽窄接口同时提供,我们称之为黑箱备忘录。
那么,如何实现宽窄接口呢,内部类就是一个很好的方法。我们可以把备忘录类设计成发起人类的内部类,但这样还有一个问题就是同一包中的其它类也能访问到这个内部类,为了解决这个问题,可以将备忘录这个内部类的方法设成私有的,这样既可以保证很好的封装性,也可以保证发起人可以访问到。
直接上代码~~
IMemento接口
public interface IMemento {
//提供一个窄接口方法,根据需要编写,可以没有方法,这里设置这个方法只是为了区分宽窄接口方法
public void narrowMethod();
}
发起人Originator
public class Originator {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
//创建一个备忘录,将当前需要保存的信息导入并实例化出一个Memento对象
//即Originator需要保存的信息state存储在Memento对象中
public IMemento createMemento() {
return new Memento(this.state);
}
//存储备忘录,将Memento导入,并将相关数据恢复
public void restoreMemento(IMemento imemento) {
Memento memento = (Memento)imemento;
this.state = memento.getState();
}
//将备忘录设成内部私有类,不允许外部访问
private class Memento implements IMemento{
private String state;
//无参构造函数
private Memento() {
super();
}
//有参构造函数,保存发起人需要保存的状态信息
private Memento(String state) {
this.state = state;
}
private String getState() {
return state;
}
private void setState(String state) {
this.state = state;
}
@Override
public void narrowMethod() {
System.out.println("这是窄接口方法!");
}
}
//提供了一个可以获取窄接口的方法
public IMemento getiMemento() {
return new Memento();
}
}
负责人Caretaker
public class Caretaker {
//这里就只能看到IMemento接口,连Memento里面的属性都看不到了
private IMemento iMemento;
public IMemento getiMemento() {
return iMemento;
}
public void setiMemento(IMemento iMemento) {
this.iMemento = iMemento;
}
}
测试类
public class Test {
public static void main(String[] args) {
//使用宽接口
Originator o = new Originator();
o.setState("On");
System.out.println("初始状态为:"+o.getState());
Caretaker c = new Caretaker();
c.setiMemento(o.createMemento());
o.setState("Off");
System.out.println("更改状态为:"+o.getState());
o.restoreMemento(c.getiMemento());
System.out.println("恢复初始状态为:"+o.getState());
//使用窄接口
IMemento memento = o.getiMemento();
memento.narrowMethod();
}
}
结果与上面一致
这里再次总结一下:
(1)发起人Originator
(2)备忘录Memento
(3)负责人Caretaker
使用备忘录模式来实现保存对象的历史状态可以很有效地保持封装边界。使用备忘录可以有效地避免暴露那些需要Originator管理但又必须存储Originator之外的一些属性,并且只对Originator开放,从而保持了封装边界。但如果Originator中需要保存的属性过多,或者需要进行频繁的创建、恢复操作,则会造成很大的开销,这就得不偿失。
关于备忘录模式应用最广泛当属自述历史模式了。这里简单介绍下,就是将发起人、负责人写在一个类中,也就是说发起人还兼顾起到负责人的作用。简单写下代码吧。
IMemento接口
public interface IMemento {
}
这里什么方法都没写,其实IMemento接口就只是起到了一个作用,使外部对象不能访问备忘录的内部信息。
负责人Caretaker,和上面的相同
public class Caretaker {
//这里就只能看到IMemento接口,连Memento里面的属性都看不到了
private IMemento iMemento;
public IMemento getiMemento() {
return iMemento;
}
public void setiMemento(IMemento iMemento) {
this.iMemento = iMemento;
}
}
发起人Originator
public class Originator {
//需要保存的状态
private String state;
//维护一个负责人角色的对象
private Caretaker caretaker = new Caretaker();
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
//创建一个备忘录,并通过自身维护的负责人角色进行存储,保存了需要保存的信息state属性值
public void createMemento() {
caretaker.setiMemento(new Memento(this.state));
}
//存储备忘录,这里略有不同,通过自身维护的负责人角色获取到备忘录信息
//恢复状态时调用
public void restoreMemento() {
Memento memento = (Memento) caretaker.getiMemento();
this.state = memento.getState();
}
//将备忘录设成内部私有类,不允许外部访问
private class Memento implements IMemento{
private String state;
//有参构造函数,保存发起人需要保存的状态信息
private Memento(String state) {
this.state = state;
}
private String getState() {
return state;
}
}
}
测试类
public class Test {
public static void main(String[] args) {
Originator o = new Originator();
o.setState("初始状态");
System.out.println(o.getState());
//创建备忘录,存储state
o.createMemento();
//修改state
o.setState("修改状态值");
System.out.println(o.getState());
//恢复初始状态
o.restoreMemento();
System.out.println(o.getState());
}
}
结果:
从测试类可以看出,客户端现在只能发现Originator这一个对象,其他一无所知,这样其实封装性更好,也使得客户端的代码更简单。
好了,就介绍这么多。累shi~~
写在最后,
本文主要是小猫看了《大话设计模式》的一些记录笔记,再加之自己的一些理解整理出此文,方便以后查阅,仅供参考。