备忘录模式可以在不破坏封装的前提下,将一个对象的状态捕捉(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#的工程化实现及扩展》
No comments:
Post a Comment