Liny_@NotePad

沉迷ACG中

存档读档的Memento——备忘录模式

YOYO posted @ 2009年4月18日 04:42 in 【OOA/D】 with tags 模式 Memento , 2344 阅读

在玩游戏的时候我们总要存档、读档,
怎样实现这个功能呢?

下面是一个简单的例子:
一个Role类,用于存储角色信息

  1. package memento.old;
  2.  
  3. public class Role {
  4.        
  5.         private String name;
  6.        
  7.         private int hp;
  8.        
  9.         private int mp;
  10.        
  11.         public Role(){
  12.         }
  13.        
  14.         public Role(String name, int hp, int mp){
  15.                 this.name = name;
  16.                 this.hp = hp;
  17.                 this.mp = mp;
  18.         }
  19.  
  20.         public int getHp() {
  21.                 return hp;
  22.         }
  23.        
  24.         public void fight(){
  25.                 this.hp = 0;
  26.                 this.mp = 0;
  27.         }
  28.  
  29.         public void setHp(int hp) {
  30.                 this.hp = hp;
  31.         }
  32.  
  33.         public int getMp() {
  34.                 return mp;
  35.         }
  36.  
  37.         public void setMp(int mp) {
  38.                 this.mp = mp;
  39.         }
  40.  
  41.         public String getName() {
  42.                 return name;
  43.         }
  44.  
  45.         public void setName(String name) {
  46.                 this.name = name;
  47.         }
  48.        
  49. }

 而这个TestCase表现了它是如何实现存档读档的:

  1. package memento.old;
  2.  
  3. import junit.framework.TestCase;
  4.  
  5. public class TestGame extends TestCase {
  6.        
  7.         public void testGame(){
  8.                 Role role = new Role("YOYO", 100, 10);
  9.                
  10.                 assertEquals(role.getHp(),100);
  11.                 assertEquals(role.getMp(),10);
  12.                
  13.                 //      新建角色对象用于备份
  14.                 Role bakRole = new Role();
  15.                 bakRole.setName(role.getName());
  16.                 bakRole.setHp(role.getHp());
  17.                 bakRole.setMp(role.getMp());
  18.                
  19.                 assertEquals(bakRole.getHp(),100);
  20.                 assertEquals(bakRole.getMp(),10);
  21.                
  22.                 //      角色战斗
  23.                 role.fight();
  24.                 assertEquals(role.getHp(),0);
  25.                 assertEquals(role.getMp(),0);
  26.                
  27.                 //      还原角色状态
  28.                 role.setHp(bakRole.getHp());
  29.                 role.setMp(bakRole.getMp());
  30.                
  31.                 assertEquals(role.getHp(),100);
  32.                 assertEquals(role.getMp(),10);
  33.         }
  34.  
  35. }

 

这个版本中布满了set方法!
这样也许你可以直接从外部修改你的角色信息,很是危险,
正好我们已经学习了原型模式,就用clone来实现它吧?
(这里的数据结构比较简单,因此我们使用浅复制就可以了)

Role类,这回实现了一个Cloneable接口:

  1. package memento.clone;
  2.  
  3. public class Role implements Cloneable{
  4.        
  5.         private String name;
  6.        
  7.         private int hp;
  8.        
  9.         private int mp;
  10.        
  11.         public Role(){
  12.         }
  13.        
  14.         public Role(String name, int hp, int mp){
  15.                 this.name = name;
  16.                 this.hp = hp;
  17.                 this.mp = mp;
  18.         }
  19.        
  20.         public int getHp() {
  21.                 return hp;
  22.         }
  23.  
  24.         public int getMp() {
  25.                 return mp;
  26.         }
  27.  
  28.         public String getName() {
  29.                 return name;
  30.         }
  31.        
  32.         public void fight(){
  33.                 this.hp = 0;
  34.                 this.mp = 0;
  35.         }
  36.  
  37.         @Override
  38.         protected Object clone() {
  39.                 Role role = null;
  40.                 try {
  41.                         role = (Role) super.clone();
  42.                 } catch (CloneNotSupportedException e) {
  43.                         e.printStackTrace();
  44.                 }
  45.                 return role;
  46.         }
  47.        
  48. }

 

[TestGame]。而备份时只需要克隆它的副本就行了:

  1. package memento.clone;
  2.  
  3. import junit.framework.TestCase;
  4.  
  5. public class TestGame extends TestCase {
  6.        
  7.         public void testGame(){
  8.                 Role role = new Role("YOYO", 100, 10);
  9.                
  10.                 assertEquals(role.getHp(),100);
  11.                 assertEquals(role.getMp(),10);
  12.                
  13.                 //      新建角色对象用于备份
  14.                 Role bakRole = (Role) role.clone();
  15.                
  16.                 assertEquals(bakRole.getHp(),100);
  17.                 assertEquals(bakRole.getMp(),10);
  18.                
  19.                 //      角色战斗
  20.                 role.fight();
  21.                 assertEquals(role.getHp(),0);
  22.                 assertEquals(role.getMp(),0);
  23.                
  24.                 //      还原角色状态
  25.                 role = (Role) bakRole.clone();
  26.                
  27.                 assertEquals(role.getHp(),100);
  28.                 assertEquals(role.getMp(),10);
  29.         }
  30.  
  31. }

 

额,还是有点问题,
我的角色信息里不想保存角色姓名,因为它根本不会改变!
这里只是一个姓名还好,但如果有很多不需要存储的数据,使用原型模式就不大合适了。

因此,我们引入这次要讲的备忘录模式吧。
首先还是Role类,它增加了一个存档的createMemento方法和一个读档的restoreMemento方法。

  1. package memento.normal;
  2.  
  3. public class Role {
  4.        
  5.         private String name;
  6.        
  7.         private int hp;
  8.        
  9.         private int mp;
  10.        
  11.         public Role(){
  12.         }
  13.        
  14.         public Role(String name, int hp, int mp){
  15.                 this.name = name;
  16.                 this.hp = hp;
  17.                 this.mp = mp;
  18.         }
  19.        
  20.         public void fight(){
  21.                 this.hp = 0;
  22.                 this.mp = 0;
  23.         }
  24.  
  25.         public int getHp() {
  26.                 return hp;
  27.         }
  28.  
  29.         public int getMp() {
  30.                 return mp;
  31.         }
  32.  
  33.         public String getName() {
  34.                 return name;
  35.         }
  36.  
  37.         public RoleMemento createMemento() {
  38.                 return new RoleMemento(name, hp, mp);
  39.         }
  40.  
  41.         public void restoreMemento(RoleMemento memento) {
  42.                 name = memento.getName();
  43.                 hp = memento.getHp();
  44.                 mp = memento.getMp();
  45.         }
  46.        
  47. }

 

接着则是RoleMemento,角色状态存储箱,它存储了我们要保存的状态。

  1. package memento.normal;
  2.  
  3. public class RoleMemento  {
  4.        
  5.         private String name;
  6.        
  7.         private int hp;
  8.        
  9.         private int mp;
  10.        
  11.         public RoleMemento(String name, int hp, int mp){
  12.                 this.name = name;
  13.                 this.hp = hp;
  14.                 this.mp = mp;
  15.         }
  16.  
  17.         public int getHp() {
  18.                 return hp;
  19.         }
  20.  
  21.         public int getMp() {
  22.                 return mp;
  23.         }
  24.  
  25.         public String getName() {
  26.                 return name;
  27.         }
  28.  
  29. }

 

除此之外,我们还需要一个Caretaker类,用来管理存档:

  1. package memento.normal;
  2.  
  3. public class Caretaker {
  4.        
  5.         private RoleMemento memento;
  6.        
  7.         public void saveMemento(RoleMemento memento){
  8.                 this.memento = memento;
  9.         }
  10.        
  11.         public RoleMemento retrieveMemento(){
  12.                 return memento;
  13.         }
  14.  
  15. }

 

再看看我们的TestGame,它由一个Caretaker来对指定角色进行存档和读档。

  1. package memento.normal;
  2.  
  3. import junit.framework.TestCase;
  4.  
  5. public class TestGame extends TestCase {
  6.        
  7.         public void testGame(){
  8.                 Role role = new Role("YOYO", 100, 10);
  9.                
  10.                 assertEquals(role.getHp(),100);
  11.                 assertEquals(role.getMp(),10);
  12.                
  13.                 //      新建备份
  14.                 Caretaker bak = new Caretaker();
  15.                 bak.saveMemento(role.createMemento());
  16.                
  17.                 //      角色战斗
  18.                 role.fight();
  19.                 assertEquals(role.getHp(),0);
  20.                 assertEquals(role.getMp(),0);
  21.                
  22.                 //      还原状态
  23.                 role.restoreMemento(bak.retrieveMemento());
  24.                
  25.                 assertEquals(role.getHp(),100);
  26.                 assertEquals(role.getMp(),10);
  27.         }
  28.  
  29. }

 

这就是一个简单的备忘录模式的实现了。
数据不会被外部随意访问。

备忘录模式(Memento)

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

适用性:

  • 必须保存一个对象在某一个时刻的(部分)状态, 这样以后需要时它才能恢复到先前的状态。
  • 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。

提到备忘录模式,就不得不提到宽接口和窄接口。

在这个例子中我们可以看到,
Role类可以访问Memento内部的数据,Memento对Role提供了一个宽接口,
而对于Caretaker,它只是实现了管理Memento的作用,但并不访问Memento内部的数据,即Memento对Caretaker提供的是窄接口。

保护其内容不被发起人(Originator)对象之外的任何对象所读取。提供了两个等效的接口:宽接口和窄接口。
我们说说宽接口和窄接口的定义:

     窄接口:负责人(Caretaker)对象(和其他除发起人对象之外的任何对象)看到的是备忘录的窄接口(narrow interface),这个窄接口只允许它把备忘录对象传给其他的对象。
     宽接口:发起人(Originator)对象可以看到一个宽接口(wide interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复发起人对象的内部状态。

这里用的例子是一个“白箱”的实现,对于“黑箱”实现及“java实现双重接口”参见《java与模式》。

* 一个Caretaker是可以保存多个Memento的,本例为了简单所以只设计了一个。


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter