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