备忘录(Memento Pattern)模式 备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是对象的行为模式。 备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捕捉住,并外部化 存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。备忘录模式常常与命令模式和迭代子模式一同使用。 常见的软件系统往往不止存储一个状态,而是需要存储多个状态。这些状态常常是一个对象历史发展的不同阶段的快照,存储这些快照的备忘录对象 叫做此对象的历史,某一个快照所处的位置叫做检查点。 备忘录角色: 备忘录角色有如下的责任。 1、将发起人(Originator)对象的内部状态存储起来,备忘录可以根据发起人对象的判断来决定存储多少 发起人(Originator)对象的内部状态。 2、备忘录可以保护其内容不被发起人对象之外的任何对象所读取。备忘录有两个等效的接口: 1、窄接口:负责人(Caretaker)对象(和其他除发起人对象之外的任何对象)看到的是备忘录的窄 接(narrow interface),这个窄接口只允许它把备忘录对象传给其他的对象; 2、宽接口:与负责人对象看到的窄接口相反的是,发起人对象可以看到一个宽接口(wide interface), 这个宽接口允许它读取所有的数据,以便根据数据恢复这个发起人对象的内部状态。853P 发起人角色: 发起人角色有如下责任: 1、创建一个含有当前的内部状态的备忘录对象。 2、使用备忘录对象存储其内部状态。 负责人角色: 负责人角色有如下的责任: 1、负责保存备忘录对象 2、不检查备忘录对象的内容。
Java代码
1: 宽接口和白箱: 2: 发起人角色 3: public class Originator{ 4: private String state; 5: 6: //工厂方法,返还一个新的备忘录对象 7: public Memento createMemento(){ 8: return new Memento(state); 9: } 10: 11: //将发起人恢复到备忘录对象所记载的状态 12: public void restoreMemento(Memento memento){ 13: this.state = memento.getState(); 14: } 15: 16: //状态的取值方法 17: public String getState(){ 18: return this.state; 19: } 20: 21: //状态的赋值方法 22: public void setState(String state){ 23: this.state = state; 24: System.out.println("Current state = " + this.state); 25: } 26: } 27: 28: 备忘录模式要求备忘录对象提供两个不同的接口:一个宽接口提供给发起人对象,另一个窄接口提供给所有其他的对象,包括负责人对象。 29: 宽接口允许发起人读取到所有的数据;窄接口只允许它把备忘录对象传给其他的对象而看不到内部的数据。 30: //备忘录角色 31: public class Memento{ 32: private String state; 33: 34: public Memento(String state){ 35: this.state = state; 36: } 37: 38: public String getState(){ 39: return this.state; 40: } 41: 42: public void setState(String state){ 43: this.state = state; 44: } 45: } 46: 47: 负责人角色负责保存备忘录对象,但是从不修改(甚至不查看)备忘录对象的内容(一个更好的实现是负责人对象根本无法从备忘录 48: 对象中读取个修改其内容) 49: 50: //负责人角色 51: public class Caretaker{ 52: private Memento memento; 53: 54: //备忘录的取值方法 55: public Memento retrieveMemento(){ 56: return this.memento; 57: } 58: 59: //备忘录的赋值方法 60: public void saveMemento(Memento memento){ 61: this.memento = memento; 62: } 63: } 64: 65: //客户端 66: public class Client{ 67: private static Originator o = new Originator(); 68: private static Caretaker c= new Caretaker(); 69: private static void main(String[] args){ 70: //该负责人对象的状态 71: o.setState("On"); 72: //创建备忘录对象,并将发起人对象的状态存储起来 73: c.saveMemento(o.createMemento()); 74: //修改发起人对象的状态 75: o.setState("Off"); 76: //恢复发起人对象的状态 77: o.restoreMemento(c.retrieveMemento()); 78: } 79: } 80: 首先将发起人对象的状态设置成“On”(或者任何有效状态),并且创建一个备忘录对象将这个状态存储起来;然后将发起人对象 81: 的状态改成“Off”(或者任何状态);最后又将发起人对象恢复到备忘录对象所存储起来的状态,即“On”状态(或者先前所 82: 存储的任何状态) 83: 84: 备忘录系统运行的时序是这样的: 85: (1)将发起人对象的状态设置成“On”。 86: (2)调用发起人角色的createMemento()方法,创建一个备忘录对象将这个状态存储起来。 87: (3)将备忘录对象存储到负责人对象中去。 88: 备忘录系统恢复的时序是这样的: 89: (1)将发起人对象的状态设置成“Off”; 90: (2)将备忘录对象从负责人对象中取出; 91: (3)将发起人对象恢复到备忘录对象所存储起来的状态,“On”状态。 92: 93: 白箱实现的优缺点 94: 白箱实现的一个明显的好处是比较简单,因此常常用做教学目的。白箱实现的一个明显的缺点是破坏对发起人状态的封装。 95: 96: 窄接口或者黑箱实现 97: //发起人角色 98: public class Originator{ 99: private String state; 100: 101: public Originator(){ 102: } 103: 104: //工厂方法,返还一个新的备忘录对象 105: public MementoIF createMemento(){ 106: return new Memento(this.state); 107: } 108: 109: //将发起人恢复到备忘录对象记录的状态 110: public void restoreMemento(MementoIF memento){ 111: Memento aMemento = (Memento)memento; 112: this.setState(aMemento.getState()); 113: } 114: 115: public String getState(){ 116: return this.state; 117: } 118: 119: public void setState(){ 120: this.state = state; 121: System.out.println("state = " + state); 122: } 123: 124: protected class Memento implements MementoIF{ 125: private String savedState; 126: private Mememto(String someState){ 127: savedState = someState; 128: } 129: 130: private void setState(String someState){ 131: savedState = someState; 132: } 133: 134: private String getState(){ 135: return savedState; 136: } 137: } 138: } 139: 140: public interface MementoIF{} 141: 142: public class Caretaker{ 143: private MementoIF memento; 144: 145: public MementoIF retrieveMemento(){ 146: return this.memento; 147: } 148: 149: public void saveMemento(MementoIF memento){ 150: this.memento = memento; 151: } 152: } 153: 154: public class Client{ 155: private static Originator o = new Originator(); 156: private static Caretaker c = new Caretaker(); 157: 158: public static void main(String args[]){ 159: //改变负责人对象的状态 160: o.setState("On"); 161: //创建备忘录对象,并将发起人对象的状态存储起来 162: c.saveMemento(o.createMemento()); 163: //修改发起人对象的状态 164: o.setState("Off"); 165: //恢复发起人对象的状态 166: o.restoreMemento(c.retrieveMemento()); 167: } 168: } 169: 170: 黑箱实现运行时的时序为; 171: (1)将发起人对象的状态设置成“On”。 172: (2)调用发起人角色的 createMemento()方法,创建一个备忘录对象将这个状态存储起来。 173: (3)将备忘录对象存储到负责人对象中去。由于负责人对象拿到的仅是 MementoIF类型,因此无法读出备忘录内部的状态。 174: 恢复时的时序为: 175: (1)将发起人对象的状态设置成“Off”; 176: (2)将备忘录对象从负责人对象中取出。注意此时仅能得到 MementoIF接口,因此无法读出此对象的内部状态 177: (3)将发起人对象的状态恢复成备忘录对象所存储起来的状态,,由于发起人对象的内部类Memento实现了MementoIF接口 178: 这个内部类是传入的备忘录对象的真实类型,因此发起人对象可以利用内部类Memento 的私有 接口读出此对象的内部状态 179: 180: 存储多个状态的备忘录模式: 181: //发起人角色 182: import java.util.Vector; 183: import java.util.Enumeration; 184: 185: public class Originator{ 186: private Vector states; 187: private int index; 188: 189: public Originator(){ 190: states = new Vector(); 191: index = 0; 192: } 193: 194: public Memento createMementor(){ 195: return new Mementor(states,index); 196: } 197: 198: public void restoreMementor(Mementor memento){ 199: states = memento.getStates(); 200: index = memento.getIndex(); 201: } 202: 203: public void setState(String state){ 204: this.states.addElement(state); 205: index ++; 206: } 207: 208: //辅助方法,打印出所有的状态 209: public void printStates(){ 210: System.out.println("Total number of states: " + index); 211: for(Enumeration e = states.elements();e.hasMoreElements();){ 212: system.out.println(e.nextElement()); 213: } 214: } 215: } 216: 217: //备忘录角色 218: import java.util.Vector; 219: 220: public class Memento{ 221: private Vector states; 222: private int index; 223: 224: public Memento(Vector states,int index){ 225: this.states = (Vector)states.clone(); 226: this.index = index; 227: } 228: 229: //状态取值方法 230: Vector getStates(){ 231: return states; 232: } 233: 234: //检查点取值方法 235: int getIndex(){ 236: return this.index; 237: } 238: } 239: ******************备忘录的构造子克隆了传入的states,然后将克隆存入到备忘录对象内部,这是一个重要的细节,因为不这样的话,将会 240: 将会造成客户端和备忘录对象持有对同一个Vector对象的引用,也可以同时修改这个Vector对象,会造成系统崩溃。 241: 242: //负责人角色 243: import java.util.Vector; 244: 245: public class Caretaker{ 246: private Originator o; 247: private Vector mementos = new Vector(); 248: private int current; 249: 250: public Caretaker(Originator o){ 251: this.o = o; 252: current = 0; 253: } 254: 255: public int createMemento(){ 256: Memento memento = o.createMemento(); 257: mementos.addElement(memento); 258: return current ++; 259: } 260: 261: //将发起人恢复到某个检查点 262: public void restoreMemento(int index){ 263: Memento memento = (Memento)mementos.elementAt(index); 264: o.restoreMemento(memento); 265: } 266: 267: //某个检查点删除 268: public void removeMemento(int index){ 269: mementos.removeElementAt(index); 270: } 271: } 272: 273: //客户端 274: public class Client{ 275: private static Originator o = new Originator(); 276: private static Caretaker c = new Caretaker(o); 277: public static void main(String[] args){ 278: //改变状态 279: o.setState("state 0"); 280: //建立一个检查点 281: c.createMemento(); 282: //改变状态 283: o.setState("state 1"); 284: 285: c.createMemento(); 286: 287: o.setState("state 2"); 288: 289: c.createMemento(); 290: 291: o.setState("state 3"); 292: 293: c.createMemento(); 294: 295: o.setState("state 4"); 296: 297: c.createMemento(); 298: 299: o.printStates(); 300: 301: //恢复到第二个检查点 302: System.out.println("Restoring to 2"); 303: 304: c.restoreMemento(2); 305: 306: o.printStates(); 307: 308: System.out.println("Restoring to 0"); 309: 310: c.restoreMemento(0); 311: 312: o.printStates(); 313: 314: System.out.println("Restoring to 3"); 315: 316: c.restoreMemento(3); 317: 318: o.printStates(); 319: 320: 321: } 322: } 323: 324: 自述历史模式(备忘录模式的一个变种): 325: //窄接口 326: public interface MementoIF{} 327: 328: //发起人角色 329: public class Originator{ 330: public String state; 331: 332: public Originator(){} 333: 334: public void changeState(String state){ 335: this.state = state; 336: System.out.println("State has been changed to : " + state); 337: } 338: 339: public Memento createMemento(){ 340: return new Memento(this); 341: } 342: 343: public void restoreMemento(MementoIF memento){ 344: Memento m = (Memento)memento; 345: changeState(m.state); 346: } 347: 348: class Memento implements MementoIF{ 349: private String state; 350: 351: private String getState(){ 352: return state; 353: } 354: 355: private Memento(Originator o){ 356: this.state = o.state; 357: } 358: } 359: } 360: 361: //客户端 362: public class Client{ 363: private static Originator o; 364: private static MementoIF memento; 365: 366: public static void main(String args[]){ 367: o = new Originator(); 368: o.changeState("State 1"); 369: memento = o.createMemento(); 370: o.changeState("State 2"); 371: o.restoreMemento(memento); 372: } 373: } 374:模式的优缺点: 由于“自述历史”作为一个备忘录模式的特殊实现形式非常简单易懂,它可能是备忘录模式最为流行的实现形式。 备忘录模式的操作过程 1、客户端为发起人角色创建一个备忘录对象。 2、调用发起人对象的某个操作,这个操作是可以撤销的。 3、检查发起人对象所出状态的有效性。检查的方式可以是发起人对象的内部自查,也可以由某个外部对象进行检查。 4、如果需要的话,将发起人的操作撤销,也就是说根据备忘录对象的记录,将发起人对象的状态恢复过来。 “假如”协议模式的操作过程: 1、将发起人对象做一个拷贝。 2、在拷贝上执行某个操作。 3、检查这个拷贝的状态是否有效和自恰。 4、如果检查结果是无效或者不自恰的,那么扔掉这个拷贝,并触发异常处理程序;相反,如果检查是有效和自恰的,那么在原对象上执行这个操作 显然这一做法对于撤销一个操作并恢复操作前状态较为复杂和困难的发起人对象来说是一个较为谨慎和有效的做法。 “假如”协议模式的优点和缺点 具体来说,这个做法的长处是可以保证发起人对象永远不会处于无效或不自恰的状态上,这样作的短处是成功的操作必须执行两次。 如果操作的成功率较低的话,这样做就比较划算,反之就不太划算。 使用备忘录模式的优点和缺点 一、备忘录模式的优点 1、有时一些发起人对象的内部信息必须保存在发起人对象以外的地方,但是必须要由发起人对象自己读取,这时, 使用备忘录模式可以把复杂的发起人内部信息对其他的对象屏蔽起来,从而可以恰当地保持封装的边界。 2、本模式简化了发起人类。发起人不再需要管理和保存其内部状态的一个个版本,客户端可以自行管理他们所需 要的这些状态的版本。 3、当发起人角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原。 二、备忘录模式的缺点: 1、如果发起人角色的状态需要完整地存储到备忘录对象中,那么在资源消耗上面备忘录对象会很昂贵。 2、当负责人角色将一个备忘录 存储起来的时候,负责人可能并不知道这个状态会占用多大的存储空间,从而无法 提醒用户一个操作是否很昂贵。882——P 3、当发起人角色的状态改变的时候,有可能这个协议无效。如果状态改变的成功率不高的话,不如采取“假如”协议模式。