备忘录模式
备忘录模式结构:

1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace DesignPattern.BehavioralPattern
7 {
8 //备忘录模式要点:
9 //定义:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以把该对象恢复到原先的状态。
10 //
11 //使用场景:
12 //1.当你需要创建对象状态快照来恢复其之前的状态时
13 //2.当直接访问对象的成员变量、 获取器或设置器将导致封装被突破时
14 //备忘录让对象自行负责创建其状态的快照。 任何其他对象都不能读取快照, 这有效地保障了数据的安全性。
15
16 /*实现方式:
17 1.确定担任原发器角色的类。 重要的是明确程序使用的一个原发器中心对象, 还是多个较小的对象。
18
19 2.创建备忘录类。 逐一声明对应每个原发器成员变量的备忘录成员变量。
20
21 3.将备忘录类设为不可变。 备忘录只能通过构造函数一次性接收数据。 该类中不能包含设置器。
22
23 4.如果你所使用的编程语言支持嵌套类, 则可将备忘录嵌套在原发器中;
24 如果不支持, 那么你可从备忘录类中抽取一个空接口, 然后让其他所有对象通过接口来引用备忘录。
25 你可在该接口中添加一些元数据操作, 但不能暴露原发器的状态。
26
27 5.在原发器中添加一个创建备忘录的方法。 原发器必须通过备忘录构造函数的一个或多个实际参数来将自身状态传递给备忘录。
28 该方法返回结果的类型必须是你在上一步中抽取的接口 (如果你已经抽取了)。
29 实际上, 创建备忘录的方法必须直接与备忘录类进行交互。
30
31 6.在原发器类中添加一个用于恢复自身状态的方法。 该方法接受备忘录对象作为参数。
32 如果你在之前的步骤中抽取了接口, 那么可将接口作为参数的类型。
33 你需要将输入对象强制转换为备忘录, 因为原发器需要拥有对该对象的完全访问权限。
34
35 7.无论负责人是命令对象、历史记录或其他完全不同的东西,它都必须要知道何时向原发器请求新的备忘录、如何存储备忘录以及何时使用特定备忘录来对原发器进行恢复。
36
37 8.负责人与原发器之间的连接可以移动到备忘录类中。在本例中,每个备忘录都必须与创建自己的原发器相连接。
38 恢复方法也可以移动到备忘录类中,但只有当备忘录类嵌套在原发器中,或者原发器类提供了足够多的设置器并可对其状态进行重写时,这种方式才能实现。
39
40 */
41 public class Contact
42 {
43 public string Name { get; set; }
44 public string Number { get; set; }
45 }
46
47 //原发器 (Originator) 类可以生成自身状态的快照, 也可以在需要时通过快照恢复自身状态。
48 public class Originator
49 {
50 List<Contact> _contacts = new List<Contact>();
51 string _date;
52 public Originator(List<Contact> contacts)
53 {
54 _contacts.AddRange(contacts);
55 }
56
57 public void AddContact(Contact contact)
58 {
59 Console.WriteLine($"Originator Add new Contact {contact.Name}");
60 _contacts.Add(contact);
61 }
62
63 public Memento Save()
64 {
65 Console.WriteLine($"Originator Saveto memento ");
66 return new Memento(_contacts);
67 }
68
69 public void Restore(Memento memento)
70 {
71 Console.WriteLine($"Originator Restore from memento ");
72 _contacts.Clear();
73 _contacts.AddRange(memento.GetContacts());
74 }
75
76 public void ShowContacts()
77 {
78 _date = DateTime.Now.ToString();
79 Console.WriteLine($"{_date} Originator include contacts:");
80 foreach (var item in _contacts)
81 {
82 Console.WriteLine($"name:{item.Name},number:{item.Number}\r\n");
83 }
84 }
85 }
86
87 //备忘录 (Memento) 是原发器状态快照的值对象 (value object)。
88 //通常做法是将备忘录设为不可变的, 并通过构造函数一次性传递数据。
89 // 备忘录类也可被嵌套在原发器中。 这样原发器就可访问备忘录的成员变量和方法, 即使这些方法被声明为私有。
90 public class Memento
91 {
92 public List<Contact> _contacts = new List<Contact>();
93 private string _date;
94 public Memento(List<Contact> contacts)
95 {
96 _contacts.AddRange(contacts);
97 _date = DateTime.Now.ToString();
98 }
99
100 public List<Contact> GetContacts()
101 {
102 return _contacts;
103 }
104
105 public string GetDate()
106 {
107 return _date;
108 }
109
110 public void ShowMemento()
111 {
112 Console.WriteLine($"{_date} Memento include contacts:");
113 foreach (var item in _contacts)
114 {
115 Console.WriteLine($"name:{item.Name},number:{item.Number}");
116 }
117 Console.WriteLine("----------------------------------------------");
118 }
119 }
120
121 //负责人 (Caretaker) 仅知道 “何时” 和 “为何” 捕捉原发器的状态, 以及何时恢复状态。
122 //负责人通过保存备忘录栈来记录原发器的历史状态。
123 //当原发器需要回溯历史状态时, 负责人将从栈中获取最顶部的备忘录, 并将其传递给原发器的恢复 (restoration) 方法。
124 public class Caretaker
125 {
126 private Dictionary<string, Memento> _mementos = new Dictionary<string, Memento>();
127 private List<string> _keys = new List<string>();
128 public Caretaker() { }
129
130 public void StoreMemento(Memento memento)
131 {
132 System.Threading.Thread.Sleep(1000);
133 _mementos.Add(memento.GetDate(), memento);
134 _keys.Add(memento.GetDate());
135 }
136
137 //另一方面, 负责人对于备忘录的成员变量和方法的访问权限非常有限: 它们只能在栈中保存备忘录, 而不能修改其状态。
138 public void Undo()
139 {
140 Console.WriteLine($"Caretaker Undo last memento");
141 if (_keys.Count > 0)
142 {
143 _mementos.Remove(_keys.Last());
144 _keys.Remove(_keys.Last());
145 }
146 }
147
148 public Memento GetMemento(string dateKey)
149 {
150 Console.WriteLine($"Caretaker GetMemento {dateKey}");
151 if (!_mementos.ContainsKey(dateKey))
152 {
153 return null;
154 }
155 return _mementos[dateKey];
156 }
157
158 public void ShowAllMementos()
159 {
160 Console.WriteLine($"Caretaker Mementos include:");
161 foreach (var item in _mementos)
162 {
163 item.Value.ShowMemento();
164 }
165 }
166 }
167
168 public class MementoClient
169 {
170
171 public static void Test()
172 {
173 List<Contact> contacts = new List<Contact>
174 {
175 new Contact { Name = "Howie",Number = "000"},
176 new Contact { Name = "Silen",Number = "111"}
177 };
178
179 Originator originator = new Originator(contacts);
180 originator.ShowContacts();
181
182 Caretaker caretaker = new Caretaker();
183
184 caretaker.StoreMemento(originator.Save());
185 caretaker.ShowAllMementos();
186
187 originator.AddContact(new Contact { Name = "Spike", Number = "222" });
188 originator.ShowContacts();
189
190 caretaker.StoreMemento(originator.Save());
191 caretaker.ShowAllMementos();
192
193 caretaker.Undo();
194 caretaker.ShowAllMementos();
195
196 }
197 }
198 }
其他结构:
基于中间接口的实现
另外一种实现方法适用于不支持嵌套类的编程语言 (没错, 我说的就是 PHP)。

-
在没有嵌套类的情况下, 你可以规定负责人仅可通过明确声明的中间接口与备忘录互动, 该接口仅声明与备忘录元数据相关的方法, 限制其对备忘录成员变量的直接访问权限。
-
另一方面, 原发器可以直接与备忘录对象进行交互, 访问备忘录类中声明的成员变量和方法。 这种方式的缺点在于你需要将备忘录的所有成员变量声明为公有。
封装更加严格的实现
如果你不想让其他类有任何机会通过备忘录来访问原发器的状态, 那么还有另一种可用的实现方式。

-
这种实现方式允许存在多种不同类型的原发器和备忘录。 每种原发器都和其相应的备忘录类进行交互。 原发器和备忘录都不会将其状态暴露给其他类。
-
负责人此时被明确禁止修改存储在备忘录中的状态。 但负责人类将独立于原发器, 因为此时恢复方法被定义在了备忘录类中。
-
每个备忘录将与创建了自身的原发器连接。 原发器会将自己及状态传递给备忘录的构造函数。 由于这些类之间的紧密联系, 只要原发器定义了合适的设置器 (setter), 备忘录就能恢复其状态。
浙公网安备 33010602011771号