概述
先從面向對象的三大特徵之一封裝說起。面向對象的封裝簡單點說就是把狀態(數據)和行爲(操作這些數據的方法)放到一起,構成一個單元,通常叫做類。一個對象的行爲是事先確定好的(靜態)一些腳本,如果對象的狀態相同,對象看起來就是一樣的。所以當我們需要把一個對象的某一時刻保存起來,那麼只需要保存它在那個時刻的狀態;相反需要恢復對象到某一時刻時,只需恢復它在那個時刻的狀態。這就是備忘錄模式的原理。
在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態。這樣以後就可將該對象恢復到原先保存的狀態。
上面是GOF對備忘錄模式的意圖描述,非常清楚,內部狀態保存到外部,再從外部恢復對象。
實現時我們通常把需要保存起來的狀態封裝爲一個對象,用這個對象作爲一個信息的載體,保存或恢復。出於避免外界對這些信息進行竄改,有必要對這個信息載體進行一個抽象,讓外界只知道這是一個信息載體,而不知道具體承載了什麼內容(窄接口);而內部可以獲得載體所載有的全部信息(寬接口)。
備忘錄模式常用來實現“撤銷/重做”。
結構
備忘錄模式的類圖:
模式的參與者只有三個,相對簡單。
1、作爲對象狀態信息載體的備忘錄對象——IMemento、Memento,其中IMemento爲對外的窄接口,而具體實現Memento則是對內的寬接口;
2、需要保存和恢復狀態的對象,成爲原發器——Originator;
3、管理和持有備忘錄的備忘錄負責人——Caretaker;
示例
有一個電子書閱讀器,人們可以用它來閱讀電子文檔。閱讀器提供了書籤的功能,用戶可以保存書籤,也可以從使用一個書籤使閱讀器變爲建立書籤時的狀態。我們簡化一下,假設閱讀器可以從書名和書的頁碼兩個參數確定自身狀態。
上面的需求很符合備忘錄模式,書籤可以看作備忘錄對象,閱讀器可以看作原發器,而隱含的書籤管理結構可以作爲負責人。
1、定義備忘錄接口IBookmark(對外窄接口)。
1: using System;
2:
3: namespace DesignPatterns.Memento
4: {
5: /// <summary>
6: /// 書籤接口(對外的窄接口)
7: /// </summary>
8: public interface IBookmark
9: { }
10: }
11:
2、閱讀器類Reader,同時以私有內部類的形式實現具體的備忘錄Bookmark(對內寬接口)。
1: using System;
2:
3: namespace DesignPatterns.Memento
4: {
5: /// <summary>
6: /// 閱讀器
7: /// </summary>
8: public class Reader
9: {
10: public Reader(string bookName, int pageNumber)
11: {
12: this.bookName = bookName;
13: this.pageNumber = pageNumber;
14: }
15:
16: //書名
17: private string bookName;
18:
19: //頁碼
20: private int pageNumber;
21:
22: //獲得一個書籤
23: public IBookmark GetBookmark()
24: {
25: Console.WriteLine("建立書籤:《" + this.bookName + "》第" + pageNumber + "頁");
26: return new Bookmark(this.bookName, this.pageNumber);
27: }
28:
29: //從書籤恢復
30: public void Restore(IBookmark bookMark)
31: {
32: Bookmark bk = (Bookmark)bookMark;
33: this.bookName = bk.BookName;
34: this.pageNumber = bk.PageNumber;
35: Console.WriteLine("恢復書籤:《" + this.bookName + "》第" + pageNumber + "頁");
36: }
37:
38: //閱讀
39: public void Read()
40: {
41: Console.WriteLine("閱讀:《" + this.bookName + "》第" + pageNumber + "頁"); //閱讀
42: pageNumber++; //翻頁
43: }
44:
45: /// <summary>
46: /// 書籤實現(用內部類的方式實現對外的保密,或者說對內的寬接口)
47: /// </summary>
48: private class Bookmark : IBookmark
49: {
50: public Bookmark(string bookName, int pageNumber)
51: {
52: this.BookName = bookName;
53: this.PageNumber = pageNumber;
54: }
55:
56: /// <summary>
57: /// 書名
58: /// </summary>
59: public string BookName { get; set; }
60:
61: /// <summary>
62: /// 頁碼
63: /// </summary>
64: public int PageNumber { get; set; }
65: }
66: }
67: }
68:
3、書籤管理器BookmarkCaretaker。
1: using System;
2: using System.Collections.Generic;
3:
4: namespace DesignPatterns.Memento
5: {
6: /// <summary>
7: /// 書籤管理器
8: /// </summary>
9: public class BookmarkCaretaker
10: {
11: public BookmarkCaretaker()
12: {
13: this.bookmarks = new Dictionary<int, IBookmark>();
14: }
15:
16: private Dictionary<int, IBookmark> bookmarks;
17:
18: public void AddBookmark(int key, IBookmark bookmark)
19: {
20: this.bookmarks.Add(key, bookmark);
21: }
22:
23: public void RemoveBookmark(int key)
24: {
25: this.bookmarks.Remove(key);
26: }
27:
28: public IBookmark GetBookmark(int key)
29: {
30: return bookmarks[key];
31: }
32: }
33: }
34:
4、測試客戶端代碼。
1: using System;
2:
3: namespace DesignPatterns.Memento
4: {
5: class Program
6: {
7: static void Main(string[] args)
8: {
9: BookmarkCaretaker caretaker = new BookmarkCaretaker();
10: Reader reader = new Reader("設計模式", 1);
11: reader.Read();
12: reader.Read();
13: reader.Read();
14:
15: caretaker.AddBookmark(1, reader.GetBookmark());
16: reader.Read();
17: reader.Read();
18:
19: caretaker.AddBookmark(2, reader.GetBookmark());
20: reader.Read();
21:
22: reader.Restore(caretaker.GetBookmark(1));
23: reader.Read();
24:
25: Console.WriteLine("按任意鍵結束...");
26: Console.ReadKey();
27: }
28: }
29: }
30:
5、運行,查看結果。