温故知新(8)——备忘录模式
概述
先从面向对象的三大特征之一封装说起。面向对象的封装简单点说就是把状态(数据)和行为(操作这些数据的方法)放到一起,构成一个单元,通常叫做类。一个对象的行为是事先确定好的(静态)一些脚本,如果对象的状态相同,对象看起来就是一样的。所以当我们需要把一个对象的某一时刻保存起来,那么只需要保存它在那个时刻的状态;相反需要恢复对象到某一时刻时,只需恢复它在那个时刻的状态。这就是备忘录模式的原理。
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
上面是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、运行,查看结果。