在玩游戏的时候我们总要存档、读档,
怎样实现这个功能呢?
下面是一个简单的例子:
一个Role类,用于存储角色信息
-
package memento. old;
-
-
public class Role {
-
-
private String name;
-
-
private int hp;
-
-
private int mp;
-
-
public Role ( ) {
-
}
-
-
this. name = name;
-
this. hp = hp;
-
this. mp = mp;
-
}
-
-
public int getHp ( ) {
-
return hp;
-
}
-
-
public void fight ( ) {
-
this. hp = 0;
-
this. mp = 0;
-
}
-
-
public void setHp ( int hp ) {
-
this. hp = hp;
-
}
-
-
public int getMp ( ) {
-
return mp;
-
}
-
-
public void setMp ( int mp ) {
-
this. mp = mp;
-
}
-
-
return name;
-
}
-
-
this. name = name;
-
}
-
-
}
而这个TestCase表现了它是如何实现存档读档的:
-
package memento. old;
-
-
import junit.framework.TestCase;
-
-
public class TestGame extends TestCase {
-
-
public void testGame ( ) {
-
Role role = new Role ( "YOYO", 100, 10 );
-
-
assertEquals (role. getHp ( ), 100 );
-
assertEquals (role. getMp ( ), 10 );
-
-
// 新建角色对象用于备份
-
Role bakRole = new Role ( );
-
bakRole. setName (role. getName ( ) );
-
bakRole. setHp (role. getHp ( ) );
-
bakRole. setMp (role. getMp ( ) );
-
-
assertEquals (bakRole. getHp ( ), 100 );
-
assertEquals (bakRole. getMp ( ), 10 );
-
-
// 角色战斗
-
role. fight ( );
-
assertEquals (role. getHp ( ), 0 );
-
assertEquals (role. getMp ( ), 0 );
-
-
// 还原角色状态
-
role. setHp (bakRole. getHp ( ) );
-
role. setMp (bakRole. getMp ( ) );
-
-
assertEquals (role. getHp ( ), 100 );
-
assertEquals (role. getMp ( ), 10 );
-
}
-
-
}
这个版本中布满了set方法!
这样也许你可以直接从外部修改你的角色信息,很是危险,
正好我们已经学习了原型模式,就用clone来实现它吧?
(这里的数据结构比较简单,因此我们使用浅复制就可以了)
Role类,这回实现了一个Cloneable接口:
-
package memento. clone;
-
-
-
private String name;
-
-
private int hp;
-
-
private int mp;
-
-
public Role ( ) {
-
}
-
-
this. name = name;
-
this. hp = hp;
-
this. mp = mp;
-
}
-
-
public int getHp ( ) {
-
return hp;
-
}
-
-
public int getMp ( ) {
-
return mp;
-
}
-
-
return name;
-
}
-
-
public void fight ( ) {
-
this. hp = 0;
-
this. mp = 0;
-
}
-
-
@Override
-
Role role = null;
-
try {
-
role = (Role ) super. clone ( );
-
e. printStackTrace ( );
-
}
-
return role;
-
}
-
-
}
[TestGame]。而备份时只需要克隆它的副本就行了:
-
package memento. clone;
-
-
import junit.framework.TestCase;
-
-
public class TestGame extends TestCase {
-
-
public void testGame ( ) {
-
Role role = new Role ( "YOYO", 100, 10 );
-
-
assertEquals (role. getHp ( ), 100 );
-
assertEquals (role. getMp ( ), 10 );
-
-
// 新建角色对象用于备份
-
Role bakRole = (Role ) role. clone ( );
-
-
assertEquals (bakRole. getHp ( ), 100 );
-
assertEquals (bakRole. getMp ( ), 10 );
-
-
// 角色战斗
-
role. fight ( );
-
assertEquals (role. getHp ( ), 0 );
-
assertEquals (role. getMp ( ), 0 );
-
-
// 还原角色状态
-
role = (Role ) bakRole. clone ( );
-
-
assertEquals (role. getHp ( ), 100 );
-
assertEquals (role. getMp ( ), 10 );
-
}
-
-
}
额,还是有点问题,
我的角色信息里不想保存角色姓名,因为它根本不会改变!
这里只是一个姓名还好,但如果有很多不需要存储的数据,使用原型模式就不大合适了。
因此,我们引入这次要讲的备忘录模式吧。
首先还是Role类,它增加了一个存档的createMemento方法和一个读档的restoreMemento方法。
-
package memento. normal;
-
-
public class Role {
-
-
private String name;
-
-
private int hp;
-
-
private int mp;
-
-
public Role ( ) {
-
}
-
-
this. name = name;
-
this. hp = hp;
-
this. mp = mp;
-
}
-
-
public void fight ( ) {
-
this. hp = 0;
-
this. mp = 0;
-
}
-
-
public int getHp ( ) {
-
return hp;
-
}
-
-
public int getMp ( ) {
-
return mp;
-
}
-
-
return name;
-
}
-
-
public RoleMemento createMemento ( ) {
-
return new RoleMemento (name, hp, mp );
-
}
-
-
public void restoreMemento (RoleMemento memento ) {
-
name = memento. getName ( );
-
hp = memento. getHp ( );
-
mp = memento. getMp ( );
-
}
-
-
}
接着则是RoleMemento,角色状态存储箱,它存储了我们要保存的状态。
-
package memento. normal;
-
-
public class RoleMemento {
-
-
private String name;
-
-
private int hp;
-
-
private int mp;
-
-
this. name = name;
-
this. hp = hp;
-
this. mp = mp;
-
}
-
-
public int getHp ( ) {
-
return hp;
-
}
-
-
public int getMp ( ) {
-
return mp;
-
}
-
-
return name;
-
}
-
-
}
除此之外,我们还需要一个Caretaker类,用来管理存档:
-
package memento. normal;
-
-
public class Caretaker {
-
-
private RoleMemento memento;
-
-
public void saveMemento (RoleMemento memento ) {
-
this. memento = memento;
-
}
-
-
public RoleMemento retrieveMemento ( ) {
-
return memento;
-
}
-
-
}
再看看我们的TestGame,它由一个Caretaker来对指定角色进行存档和读档。
-
package memento. normal;
-
-
import junit.framework.TestCase;
-
-
public class TestGame extends TestCase {
-
-
public void testGame ( ) {
-
Role role = new Role ( "YOYO", 100, 10 );
-
-
assertEquals (role. getHp ( ), 100 );
-
assertEquals (role. getMp ( ), 10 );
-
-
// 新建备份
-
Caretaker bak = new Caretaker ( );
-
bak. saveMemento (role. createMemento ( ) );
-
-
// 角色战斗
-
role. fight ( );
-
assertEquals (role. getHp ( ), 0 );
-
assertEquals (role. getMp ( ), 0 );
-
-
// 还原状态
-
role. restoreMemento (bak. retrieveMemento ( ) );
-
-
assertEquals (role. getHp ( ), 100 );
-
assertEquals (role. getMp ( ), 10 );
-
}
-
-
}
这就是一个简单的备忘录模式的实现了。
数据不会被外部随意访问。
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
适用性:
- 必须保存一个对象在某一个时刻的(部分)状态, 这样以后需要时它才能恢复到先前的状态。
- 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。
提到备忘录模式,就不得不提到宽接口和窄接口。
在这个例子中我们可以看到,
Role类可以访问Memento内部的数据,Memento对Role提供了一个宽接口,
而对于Caretaker,它只是实现了管理Memento的作用,但并不访问Memento内部的数据,即Memento对Caretaker提供的是窄接口。
保护其内容不被发起人(Originator)对象之外的任何对象所读取。提供了两个等效的接口:宽接口和窄接口。
我们说说宽接口和窄接口的定义:
窄接口:负责人(Caretaker)对象(和其他除发起人对象之外的任何对象)看到的是备忘录的窄接口(narrow interface),这个窄接口只允许它把备忘录对象传给其他的对象。
宽接口:发起人(Originator)对象可以看到一个宽接口(wide interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复发起人对象的内部状态。
这里用的例子是一个“白箱”的实现,对于“黑箱”实现及“java实现双重接口”参见《java与模式》。
* 一个Caretaker是可以保存多个Memento的,本例为了简单所以只设计了一个。