2020年8月24日星期一

设计模式 备忘录模式

备忘录模式可以在不破坏封装的前提下,将一个对象的状态捕捉(Capture)住,并在外部存储,从而可以在需要的时候把这个对象还原到存储起来的状态。备忘录模式常常与命令模式和迭代器模式一同使用。

GOF对备忘录模式的描述为:
Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later.
— Design Patterns : Elements of Reusable Object-Oriented Software

备忘录模式的UML类图:

备忘录模式所涉及的角色有三个:
备忘录(Memento)
备忘录角色用来存储发起人(Originator)内部状态的快照,而且可以保护这些内容不被发起人对象之外的任何对象所读取。

发起人(Originator)
发起人负责创建一个含有当前的内部状态的备忘录对象,并保存到备忘录中。

负责人(Caretaker)
负责人用来维护发起人保存的一个或多个备忘录,并在需要回滚状态的时候提供保存了相应状态的备忘录。

宽接口与窄接口

窄接口:负责人(Caretaker)对象(和其他除发起人对象之外的任何对象)看到的是备忘录的窄接口(narrow interface),这个窄接口只允许它把备忘录对象传给其他的对象。
宽接口:与负责人对象看到的窄接口相反的是,发起人对象可以看到一个宽接口(wide interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。

备忘录与负责人之间除了使用窄接口,也有直接使用了宽接口的实现方式,这种方式原则上是破坏了封装性的。但是开发者之间的约定,同样可以在一定程度上实现备忘录模式的大部分用意。

宽接口实现

public class Memento{ public string State { get; set; } public Memento(string state) {  this.State = state; }}public class Originator{ private string state; public string State {  get  {   return state;  }  set  {   state = value;   Console.WriteLine(state);  } } public Memento CreateMemento() {  return new Memento(state); } //将发起人恢复到备忘录对象所记载的状态 public void RestoreMemento(Memento memento) {  this.State = memento.State; }}public class Caretaker{ private Memento memento; //备忘录的取值方法 public Memento RetrieveMemento() {  return this.memento; } //备忘录的赋值方法 public void SaveMemento(Memento memento) {  this.memento = memento; }}

调用端

public class WildClient{ public static void Entry() {  Originator originator = new Originator();  originator.State = "ON";  Caretaker caretaker = new Caretaker();  caretaker.SaveMemento(originator.CreateMemento());  originator.State = "OFF";  originator.RestoreMemento(caretaker.RetrieveMemento()); }}

在这段代码中,首先将发起人对象的状态设置成"ON",并创建一个备忘录对象将这个状态存储起来;然后将发起人对象的状态改成"OFF";最后又将发起人对象恢复到备忘录对象所存储起来的状态,即"ON"状态。

但这种宽接口实现方式,负责人维护的Memento,是任何类型都可以访问或修改的。

窄接口实现

窄接口要求备忘录角色对发起人(Originator)角色对象提供一个宽接口,而为其他对象提供一个窄接口。为了实现这里要求的双重接口,在C#中可以采用将备忘录角色类设计成发起人角色类的内部类的方式。
将Memento设成Originator类的内部类,从而将Memento对象封装在Originator里面;在外部提供一个标识接口IMemento给Caretaker以及其他对象。这样,Originator类看到的是Menmento的所有接口,而Caretaker以及其他对象看到的仅仅是标识接口MementoIF所暴露出来的接口。

public interface IMemento { }public class Originator{ private class Memento : IMemento {  public string State { get; set; }  public Memento(string state)  {   this.State = state;  } } private string state; public string State {cccxcxcxxc   }  se  {   state = value;   Console.WriteLine(state);  } } public IMemento CreateMemento() {  return new Memento(state); } //将发起人恢复到备忘录对象所记载的状态 public void RestoreMemento(IMemento memento) {  if (memento == null)  {   return;  }  this.State = (memento as Memento).State; }}public class Caretaker{ private IMemento memento; //备忘录的取值方法 public IMemento RetrieveMemento() {  return this.memento; } //备忘录的赋值方法 public void SaveMemento(IMemento memento) {  this.memento = memento; }}

由于IMemento只是一个标识接口,并没有任何方法,所以CareTaker无法修改其维护的IMemento实例。

多个检查点

前面的实现中备忘录只保存了发起人的一个状态,但很多时候这是无法满足需求的,比如游戏不可能只让玩家保存一个检查点。前面的代码只需修改CareTaker就可以实现多个检查点,将备忘录对象压入栈中,恢复时弹出即可:

public class MultiCaretaker{ private Stack<IMemento> mementos = new Stack<IMemento>(); //备忘录的取值方法 public IMemento RetrieveMemento() {  if (mementos.Count == 0)  {   return null;  }  return mementos.Pop(); } //备忘录的赋值方法 public void SaveMemento(IMemento memento) {  mementos.Push(memento); }}

调用端

public class MultiCheckpoint{ public static void Entry() {  Originator originator = new Originator();  originator.State = "ON";  MultiCaretaker caretaker = new MultiCaretaker();  caretaker.SaveMemento(originator.CreateMemento());  originator.State = "Volume 1";  caretaker.SaveMemento(originator.CreateMemento());  originator.State = "Volume 2";  caretaker.SaveMemento(originator.CreateMemento());  originator.State = "Volume 3";  caretaker.SaveMemento(originator.CreateMemento());  originator.State = "OFF";  originator.RestoreMemento(caretaker.RetrieveMemento());  originator.RestoreMemento(caretaker.RetrieveMemento());  originator.RestoreMemento(caretaker.RetrieveMemento());  originator.RestoreMemento(caretaker.RetrieveMemento()); }}

参考书籍:
王翔著 《设计模式——基于C#的工程化实现及扩展》

设计模式 备忘录模式浩方damai亚马逊常见收款方式选品逻辑指南:高效放大选品成功率揭秘:真正的跨境电商人才都去哪里了?速卖通开店问题、速卖通开店流程详解清远美食有哪些?惠州龙门温泉就是龙门铁泉吗?暑假去珠海御温泉泡汤好不好?