存档读档的Memento——备忘录模式
在玩游戏的时候我们总要存档、读档,
怎样实现这个功能呢?
下面是一个简单的例子:
一个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);
-
}
-
-
}
这就是一个简单的备忘录模式的实现了。
数据不会被外部随意访问。
备忘录模式(Memento)
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
适用性:
- 必须保存一个对象在某一个时刻的(部分)状态, 这样以后需要时它才能恢复到先前的状态。
- 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。
提到备忘录模式,就不得不提到宽接口和窄接口。
在这个例子中我们可以看到,
Role类可以访问Memento内部的数据,Memento对Role提供了一个宽接口,
而对于Caretaker,它只是实现了管理Memento的作用,但并不访问Memento内部的数据,即Memento对Caretaker提供的是窄接口。
保护其内容不被发起人(Originator)对象之外的任何对象所读取。提供了两个等效的接口:宽接口和窄接口。
我们说说宽接口和窄接口的定义:
窄接口:负责人(Caretaker)对象(和其他除发起人对象之外的任何对象)看到的是备忘录的窄接口(narrow interface),这个窄接口只允许它把备忘录对象传给其他的对象。
宽接口:发起人(Originator)对象可以看到一个宽接口(wide interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复发起人对象的内部状态。
这里用的例子是一个“白箱”的实现,对于“黑箱”实现及“java实现双重接口”参见《java与模式》。
* 一个Caretaker是可以保存多个Memento的,本例为了简单所以只设计了一个。