备忘录模式

备忘录模式结构:

 

 

 

  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

不使用嵌套类的备忘录
  1. 在没有嵌套类的情况下 你可以规定负责人仅可通过明确声明的中间接口与备忘录互动 该接口仅声明与备忘录元数据相关的方法 限制其对备忘录成员变量的直接访问权限

  2. 另一方面 原发器可以直接与备忘录对象进行交互 访问备忘录类中声明的成员变量和方法 这种方式的缺点在于你需要将备忘录的所有成员变量声明为公有

封装更加严格的实现

如果你不想让其他类有任何机会通过备忘录来访问原发器的状态 那么还有另一种可用的实现方式

封装更加严格的备忘录
  1. 这种实现方式允许存在多种不同类型的原发器和备忘录 每种原发器都和其相应的备忘录类进行交互 原发器和备忘录都不会将其状态暴露给其他类

  2. 负责人此时被明确禁止修改存储在备忘录中的状态 但负责人类将独立于原发器 因为此时恢复方法被定义在了备忘录类中

  3. 每个备忘录将与创建了自身的原发器连接 原发器会将自己及状态传递给备忘录的构造函数 由于这些类之间的紧密联系 只要原发器定义了合适的设置器 (setter 备忘录就能恢复其状态

 

posted on 2021-07-22 16:32  HowieGo  阅读(53)  评论(0)    收藏  举报

导航