《java设计模式》之备忘录模式

备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式。是对象的行为模式。

  备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捕捉(Capture)住,并外部化。存储起来,从而能够在将来合适的时候把这个对象还原到存储起来的状态。

备忘录模式经常与命令模式和迭代子模式一同使用。

备忘录模式的结构

  备忘录模式的结构图例如以下所看到的

  备忘录模式所涉及的角色有三个:备忘录(Memento)角色、发起人(Originator)角色、负责人(Caretaker)角色

  备忘录(Memento)角色

  备忘录角色又例如以下责任:

  (1)将发起人(Originator)对象的内战状态存储起来。

备忘录能够依据发起人对象的推断来决定存储多少发起人(Originator)对象的内部状态。

  (2)备忘录能够保护其内容不被发起人(Originator)对象之外的不论什么对象所读取。

  备忘录有两个等效的接口:

  ●  窄接口:负责人(Caretaker)对象(和其它除发起人对象之外的不论什么对象)看到的是备忘录的窄接口(narrow interface),这个窄接口仅仅同意它把备忘录对象传给其它的对象。

  ●  宽接口:与负责人对象看到的窄接口相反的是,发起人对象能够看到一个宽接口(wide interface),这个宽接口同意它读取全部的数据,以便依据这些数据恢复这个发起人对象的内部状态。

  发起人(Originator)角色

  发起人角色有例如以下责任:

  (1)创建一个含有当前的内部状态的备忘录对象。

  (2)使用备忘录对象存储其内部状态。

  负责人(Caretaker)角色

  负责人角色有例如以下责任:

  (1)负责保存备忘录对象。

  (2)不检查备忘录对象的内容。

 “白箱”备忘录模式的实现

  备忘录角色对不论什么对象都提供一个接口。即宽接口。备忘录角色的内部所存储的状态就对全部对象公开。因此这个实现又叫做“白箱实现”。

  “白箱”实现将发起人角色的状态存储在一个大家都看得到的地方,因此是破坏封装性的。可是通过程序猿自律。相同能够在一定程度上实现模式的大部分用意。因此白箱实现仍然是有意义的。

  以下给出一个示意性的“白箱实现”。

  源码

  发起人角色类,发起人角色利用一个新创建的备忘录对象将自己的内部状态存储起来。

  1. package com.bankht.Memento;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-7-2 下午04:12:20 
  6.  *  
  7.  * @类说明 :发起人角色类,发起人角色利用一个新创建的备忘录对象将自己的内部状态存储起来。

     

  8.  */  
  9. public class Originator {  
  10.   
  11.     private String state;  
  12.   
  13.     /** 
  14.      * 工厂方法。返回一个新的备忘录对象 
  15.      */  
  16.     public Memento createMemento() {  
  17.         return new Memento(state);  
  18.     }  
  19.   
  20.     /** 
  21.      * 将发起人恢复到备忘录对象所记载的状态 
  22.      */  
  23.     public void restoreMemento(Memento memento) {  
  24.         this.state = memento.getState();  
  25.     }  
  26.   
  27.     public String getState() {  
  28.         return state;  
  29.     }  
  30.   
  31.     public void setState(String state) {  
  32.         this.state = state;  
  33.         System.out.println("当前状态:" + this.state);  
  34.     }  
  35.   
  36. }  


 

  备忘录角色类,备忘录对象将发起人对象传入的状态存储起来。

  1. package com.bankht.Memento;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-7-2 下午04:12:40 
  6.  *  
  7.  * @类说明 :备忘录角色类,备忘录对象将发起人对象传入的状态存储起来。 
  8.  */  
  9. public class Memento {  
  10.   
  11.     private String state;  
  12.   
  13.     public Memento(String state) {  
  14.         this.state = state;  
  15.     }  
  16.   
  17.     public String getState() {  
  18.         return state;  
  19.     }  
  20.   
  21.     public void setState(String state) {  
  22.         this.state = state;  
  23.     }  
  24.   
  25. }  


 

  负责人角色类。负责人角色负责保存备忘录对象。可是从不改动(甚至不查看)备忘录对象的内容。

  1. package com.bankht.Memento;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-7-2 下午04:13:03 
  6.  *  
  7.  * @类说明 :负责人角色类,负责人角色负责保存备忘录对象,可是从不改动(甚至不查看)备忘录对象的内容。 
  8.  */  
  9. public class Caretaker {  
  10.   
  11.     private Memento memento;  
  12.   
  13.     /** 
  14.      * 备忘录的取值方法 
  15.      */  
  16.     public Memento retrieveMemento() {  
  17.         return this.memento;  
  18.     }  
  19.   
  20.     /** 
  21.      * 备忘录的赋值方法 
  22.      */  
  23.     public void saveMemento(Memento memento) {  
  24.         this.memento = memento;  
  25.     }  
  26. }  


 

  client角色类

  1. package com.bankht.Memento;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-7-2 下午04:13:24 
  6.  *  
  7.  * @类说明 :client角色类 
  8.  */  
  9. public class Client {  
  10.   
  11.     public static void main(String[] args) {  
  12.   
  13.         Originator o = new Originator();  
  14.         Caretaker c = new Caretaker();  
  15.         // 改变负责人对象的状态  
  16.         o.setState("On");  
  17.         // 创建备忘录对象,并将发起人对象的状态储存起来  
  18.         c.saveMemento(o.createMemento());  
  19.         // 改动发起人的状态  
  20.         o.setState("Off");  
  21.         // 恢复发起人对象的状态  
  22.         o.restoreMemento(c.retrieveMemento());  
  23.   
  24.         System.out.println(o.getState());  
  25.     }  
  26.   
  27. }  


 

  在上面的这个示意性的client角色里面,首先将发起人对象的状态设置成“On”。并创建一个备忘录对象将这个状态存储起来。然后将发起人对象的状态改成“Off”;最后又将发起人对象恢复到备忘录对象所存储起来的状态。即“On”状态。

  系统的时序图更可以反映出系统各个角色被调用的时间顺序。

例如以下图是将发起人对象的状态存储到白箱备忘录对象中去的时序图。

  能够看出系统执行的时序是这种:

  (1)将发起人对象的状态设置成“On”。

  (2)调用发起人角色的createMemento()方法,创建一个备忘录对象将这个状态存储起来。

  (3)将备忘录对象存储到负责人对象中去。

  将发起人对象恢复到备忘录对象所记录的状态的时序图例如以下所看到的:

  能够看出。将发起人对象恢复到备忘录对象所记录的状态时。系统的执行时序是这种:

  (1)将发起人状态设置成“Off”。

  (2)将备忘录对象从负责人对象中取出。

  (3)将发起人对象恢复到备忘录对象所存储起来的状态,即“On”状态。

 “黑箱”备忘录模式的实现

  备忘录角色对发起人(Originator)角色对象提供一个宽接口,而为其它对象提供一个窄接口。

这种实现叫做“黑箱实现”。

  在JAVA语言中,实现双重接口的办法就是将备忘录角色类设计成发起人角色类的内部成员类。

  将Memento设成Originator类的内部类,从而将Memento对象封装在Originator里面;在外部提供一个标识接口MementoIF给Caretaker以及其它对象。

这样,Originator类看到的是Menmento的全部接口,而Caretaker以及其它对象看到的不过标识接口MementoIF所暴露出来的接口。

  使用内部类实现备忘录模式的类图例如以下所看到的。

  源码

  发起人角色类Originator中定义了一个内部的Memento类。因为此Memento类的所有接口都是私有的,因此仅仅有它自己和发起人类能够调用。

  1. package com.bankht.Memento.sample2;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-7-2 下午04:33:55 
  6.  *  
  7.  * @类说明  
  8.  *      :发起人角色类Originator中定义了一个内部的Memento类。

     

  9.  *        因为此Memento类的所有接口都是私有的,因此仅仅有它自己和发起人类能够调用。

     

  10.  */  
  11. public class Originator {  
  12.   
  13.     private String state;  
  14.   
  15.     public String getState() {  
  16.         return state;  
  17.     }  
  18.   
  19.     public void setState(String state) {  
  20.         this.state = state;  
  21.         System.out.println("赋值状态:" + state);  
  22.     }  
  23.   
  24.     /** 
  25.      * 工厂方法,返还一个新的备忘录对象 
  26.      */  
  27.     public MementoIF createMemento() {  
  28.         return new Memento(state);  
  29.     }  
  30.   
  31.     /** 
  32.      * 发起人恢复到备忘录对象记录的状态 
  33.      */  
  34.     public void restoreMemento(MementoIF memento) {  
  35.         this.setState(((Memento) memento).getState());  
  36.     }  
  37.   
  38.     private class Memento implements MementoIF {  
  39.   
  40.         private String state;  
  41.   
  42.         /** 
  43.          * 构造方法 
  44.          */  
  45.         private Memento(String state) {  
  46.             this.state = state;  
  47.         }  
  48.   
  49.         private String getState() {  
  50.             return state;  
  51.         }  
  52.   
  53.         private void setState(String state) {  
  54.             this.state = state;  
  55.         }  
  56.     }  
  57. }  


 

  窄接口MementoIF,这是一个标识接口,因此它未定义出不论什么的方法。

  1. package com.bankht.Memento.sample2;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-7-2 下午04:34:25 
  6.  *  
  7.  * @类说明 :窄接口MementoIF,这是一个标识接口,因此它未定义出不论什么的方法。 
  8.  */  
  9. public interface MementoIF {  
  10.   
  11. }  


 

  负责人角色类Caretaker可以得到的备忘录对象是以MementoIF为接口的,因为这个接口不过一个标识接口。因此负责人角色不可能改变这个备忘录对象的内容。

  1. package com.bankht.Memento.sample2;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-7-2 下午04:35:20 
  6.  *  
  7.  * @类说明 : 
  8.  *          负责人角色类Caretaker可以得到的备忘录对象是以MementoIF为接口的。因为这个接口不过一个标识接口, 
  9.  *          因此负责人角色不可能改变这个备忘录对象的内容。

     

  10.  */  
  11. public class Caretaker {  
  12.   
  13.     private MementoIF memento;  
  14.   
  15.     /** 
  16.      * 备忘录取值方法 
  17.      */  
  18.     public MementoIF retrieveMemento() {  
  19.         return memento;  
  20.     }  
  21.   
  22.     /** 
  23.      * 备忘录赋值方法 
  24.      */  
  25.     public void saveMemento(MementoIF memento) {  
  26.         this.memento = memento;  
  27.     }  
  28. }  


 

  client角色类

  1. package com.bankht.Memento.sample2;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-7-2 下午04:36:01 
  6.  *  
  7.  * @类说明 :client角色类 
  8.  */  
  9. public class Client {  
  10.   
  11.     public static void main(String[] args) {  
  12.         Originator o = new Originator();  
  13.         Caretaker c = new Caretaker();  
  14.         // 改变负责人对象的状态  
  15.         o.setState("On");  
  16.         // 创建备忘录对象,并将发起人对象的状态存储起来  
  17.         c.saveMemento(o.createMemento());  
  18.         // 改动发起人对象的状态  
  19.         o.setState("Off");  
  20.         // 恢复发起人对象的状态  
  21.         o.restoreMemento(c.retrieveMemento());  
  22.     }  
  23.   
  24. }  


 

  client首先

  (1)将发起人对象的状态设置为“On”。

  (2)调用createMemento()方法。创建一个备忘录对象将这个状态存储起来(此时createMemento()方法还回的明显类型是MementoIF接口,真实类型为Originator内部的Memento对象)

  (3)将备忘录对象存储到负责人对象中去。因为负责人对象拿到的仅是MementoIF接口,因此无法读出备忘录对象内部的状态。

  (4)将发起人对象的状态设置为“Off”。

  (5)调用负责人对象的retrieveMemento()方法将备忘录对象取出。注意此时仅能得到MementoIF接口,因此无法读出此对象的内部状态。

  (6)调用发起人对象的restoreMemento()方法将发起人对象的状态恢复成备忘录对象所存储的起来的状态,即“On”状态。

因为发起人对象的内部类Memento实现了MementoIF接口,这个内部类是传入的备忘录对象的真实类型,因此发起人对象能够利用内部类Memento的私有接口读出此对象的内部状态。

 多重检查点

  前面所给出的白箱和黑箱的示意性实现都是仅仅存储一个状态的简单实现,也能够叫做仅仅有一个检查点。常见的系统往往须要存储不止一个状态,而是须要存储多个状态。或者叫做有多个检查点。

  备忘录模式能够将发起人对象的状态存储到备忘录对象里面。备忘录模式能够将发起人对象恢复到备忘录对象所存储的某一个检查点上。以下给出一个示意性的、有多重检查点的备忘录模式的实现。

  源码

  发起人角色源码

  1. package com.bankht.Memento.Multiple;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. /** 
  7.  * @author: 特种兵—AK47 
  8.  * @创建时间:2012-7-2 下午04:55:00 
  9.  *  
  10.  * @类说明 :发起人角色源码 
  11.  */  
  12. public class Originator {  
  13.   
  14.     private List<String> states;  
  15.     // 检查点指数  
  16.     private int index;  
  17.   
  18.     /** 
  19.      * 构造函数 
  20.      */  
  21.     public Originator() {  
  22.         states = new ArrayList<String>();  
  23.         index = 0;  
  24.     }  
  25.   
  26.     /** 
  27.      * 工厂方法,返还一个新的备忘录对象 
  28.      */  
  29.     public Memento createMemento() {  
  30.         return new Memento(states, index);  
  31.     }  
  32.   
  33.     /** 
  34.      * 将发起人恢复到备忘录对象记录的状态上 
  35.      */  
  36.     public void restoreMemento(Memento memento) {  
  37.         states = memento.getStates();  
  38.         index = memento.getIndex();  
  39.     }  
  40.   
  41.     /** 
  42.      * 状态的赋值方法 
  43.      */  
  44.     public void setState(String state) {  
  45.         states.add(state);  
  46.         index++;  
  47.     }  
  48.   
  49.     /** 
  50.      * 辅助方法,打印全部状态 
  51.      */  
  52.     public void printStates() {  
  53.         System.out.println("检查点共同拥有:"+states.size()+"个");  
  54.         for (String state : states) {  
  55.             System.out.println(state);  
  56.         }  
  57.     }  
  58. }  


  备忘录角色类,这个实现能够存储随意多的状态,外界能够使用检查点指数index来取出检查点上的状态。 

  1. package com.bankht.Memento.Multiple;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. /** 
  7.  * @author: 特种兵—AK47 
  8.  * @创建时间:2012-7-2 下午04:55:23 
  9.  *  
  10.  * @类说明 :备忘录角色类,这个实现能够存储随意多的状态。外界能够使用检查点指数index来取出检查点上的状态。   
  11.  */  
  12. public class Memento {  
  13.   
  14.     private List<String> states;  
  15.     private int index;  
  16.   
  17.     /** 
  18.      * 构造函数 
  19.      */  
  20.     public Memento(List<String> states, int index) {  
  21.         this.states = new ArrayList<String>(states);  
  22.         this.index = index;  
  23.     }  
  24.   
  25.     public List<String> getStates() {  
  26.         return states;  
  27.     }  
  28.   
  29.     public int getIndex() {  
  30.         return index;  
  31.     }  
  32.   
  33. }  

  负责人角色类

  1. package com.bankht.Memento.Multiple;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. /** 
  7.  * @author: 特种兵—AK47 
  8.  * @创建时间:2012-7-2 下午04:56:08 
  9.  *  
  10.  * @类说明 :负责人角色类 
  11.  */  
  12. public class Caretaker {  
  13.   
  14.     private Originator o;  
  15.     private List<Memento> mementos = new ArrayList<Memento>();  
  16.     private int current;  
  17.   
  18.     /** 
  19.      * 构造函数 
  20.      */  
  21.     public Caretaker(Originator o) {  
  22.         this.o = o;  
  23.         current = 0;  
  24.     }  
  25.   
  26.     /** 
  27.      * 创建一个新的检查点 
  28.      */  
  29.     public int createMemento() {  
  30.         Memento memento = o.createMemento();  
  31.         mementos.add(memento);  
  32.         return current++;  
  33.     }  
  34.   
  35.     /** 
  36.      * 将发起人恢复到某个检查点 
  37.      */  
  38.     public void restoreMemento(int index) {  
  39.         Memento memento = mementos.get(index);  
  40.         o.restoreMemento(memento);  
  41.     }  
  42.   
  43.     /** 
  44.      * 将某个检查点删除 
  45.      */  
  46.     public void removeMemento(int index) {  
  47.         mementos.remove(index);  
  48.     }  
  49. }  

  client角色源码

  1. package com.bankht.Memento.Multiple;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-7-2 下午04:56:31 
  6.  *  
  7.  * @类说明 :client角色源码 
  8.  */  
  9. public class Client {  
  10.   
  11.     public static void main(String[] args) {  
  12.   
  13.         Originator o = new Originator();  
  14.         Caretaker c = new Caretaker(o);  
  15.         // 改变状态  
  16.         o.setState("state 0");  
  17.         // 建立一个检查点  
  18.         c.createMemento();  
  19.         // 改变状态  
  20.         o.setState("state 1");  
  21.         // 建立一个检查点  
  22.         c.createMemento();  
  23.         // 改变状态  
  24.         o.setState("state 2");  
  25.         // 建立一个检查点  
  26.         c.createMemento();  
  27.         // 改变状态  
  28.         o.setState("state 3");  
  29.         // 建立一个检查点  
  30.         c.createMemento();  
  31.         // 打印出全部检查点  
  32.         o.printStates();  
  33.         System.out.println("-----------------恢复检查点-----------------");  
  34.         // 恢复到第二个检查点  
  35.         c.restoreMemento(2);  
  36.         // 打印出全部检查点  
  37.         o.printStates();  
  38.     }  
  39.   
  40. }  

 执行结果例如以下:

  1. 检查点共同拥有:4个  
  2. state 0  
  3. state 1  
  4. state 2  
  5. state 3  
  6. -----------------恢复检查点-----------------  
  7. 检查点共同拥有:3个  
  8. state 0  
  9. state 1  
  10. state 2  


 

  能够看出,client角色通过不断改变发起人角色的状态,并将之存储在备忘录里面。通过指明检查点指数能够将发起人角色恢复到相应的检查点所相应的状态上。

  将发起人的状态存储到备忘录对象中的活动序列图例如以下:


  系统执行的时序是这种:

  (1)将发起人对象的状态设置成某个有效状态;

  (2)调用负责人角色的createMemento()方法,负责人角色会负责调用发起人角色和备忘录角色,将发起人对象的状态存储起来。

  将发起人对象恢复到某一个备忘录对象的检查点的活动序列图例如以下:


 

  因为负责人角色的功能被增强了。因此将发起人对象恢复到备忘录对象所记录的状态时。系统执行的时序被简化了:

  (1)调用负责人角色的restoreMemento()方法,将发起人恢复到某个检查点。

 “自述历史”模式

  所谓“自述历史”模式(History-On-Self Pattern)实际上就是备忘录模式的一个变种。

在备忘录模式中,发起人(Originator)角色、负责人(Caretaker)角色和备忘录(Memento)角色都是独立的角色。尽管在实现上备忘录类能够成为发起人类的内部成员类,可是备忘录类仍然保持作为一个角色的独立意义。在“自述历史”模式里面,发起人角色自己兼任负责人角色。

  “自述历史”模式的类图例如以下所看到的:

  备忘录角色有例如以下责任:

  (1)将发起人(Originator)对象的内部状态存储起来。

  (2)备忘录能够保护其内容不被发起人(Originator)对象之外的不论什么对象所读取。

  发起人角色有例如以下责任:

  (1)创建一个含有它当前的内部状态的备忘录对象。

  (2)使用备忘录对象存储其内部状态。

  client角色有负责保存备忘录对象的责任。

  源码

  窄接口MementoIF,这是一个标识接口。因此它未定义出不论什么的方法。

  1. package com.bankht.Memento.HistoryOnSelf;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-7-2 下午05:02:31 
  6.  *  
  7.  * @类说明 :窄接口MementoIF。这是一个标识接口。因此它未定义出不论什么的方法。 
  8.  */  
  9. public interface MementoIF {  
  10.   
  11. }  


 发起人角色同一时候还兼任负责人角色。也就是说它自己负责保持自己的备忘录对象。

  1. package com.bankht.Memento.HistoryOnSelf;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-7-2 下午05:02:57 
  6.  *  
  7.  * @类说明 :发起人角色同一时候还兼任负责人角色,也就是说它自己负责保持自己的备忘录对象。 
  8.  */  
  9. public class Originator {  
  10.   
  11.     public String state;  
  12.   
  13.     /** 
  14.      * 改变状态 
  15.      */  
  16.     public void changeState(String state) {  
  17.         this.state = state;  
  18.         System.out.println("状态改变为:" + state);  
  19.     }  
  20.   
  21.     /** 
  22.      * 工厂方法。返还一个新的备忘录对象 
  23.      */  
  24.     public Memento createMemento() {  
  25.         return new Memento(this);  
  26.     }  
  27.   
  28.     /** 
  29.      * 将发起人恢复到备忘录对象所记录的状态上 
  30.      */  
  31.     public void restoreMemento(MementoIF memento) {  
  32.         Memento m = (Memento) memento;  
  33.         changeState(m.state);  
  34.     }  
  35.   
  36.     private class Memento implements MementoIF {  
  37.   
  38.         private String state;  
  39.   
  40.         /** 
  41.          * 构造方法 
  42.          */  
  43.         private Memento(Originator o) {  
  44.             this.state = o.state;  
  45.         }  
  46.   
  47.         private String getState() {  
  48.             return state;  
  49.         }  
  50.   
  51.     }  
  52. }  


 client角色类

  1. package com.bankht.Memento.HistoryOnSelf;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-7-2 下午05:03:29 
  6.  *  
  7.  * @类说明 :client角色类 
  8.  */  
  9. public class Client {  
  10.   
  11.     public static void main(String[] args) {  
  12.         Originator o = new Originator();  
  13.         // 改动状态  
  14.         o.changeState("state 0");  
  15.         // 创建备忘录  
  16.         MementoIF memento = o.createMemento();  
  17.         // 改动状态  
  18.         o.changeState("state 1");  
  19.         // 依照备忘录恢复对象的状态  
  20.         o.restoreMemento(memento);  
  21.     }  
  22.   
  23. }  


 

  因为“自述历史”作为一个备忘录模式的特殊实现形式很easy易懂,它可能是备忘录模式最为流行的实现形式。


 

本文參考内容:http://blog.csdn.net/m13666368773/article/details/7709156

中文版的  java设计模式第二版


posted on 2017-06-21 20:01  ljbguanli  阅读(228)  评论(0编辑  收藏  举报