備忘錄(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、當發起人角色的狀態改變的時候,有可能這個協議無效。如果狀態改變的成功率不高的話,不如采取“假如”協議模式。