Refer to:http://blog.sina.com.cn/s/blog_4a2100f8010142n1.html
1.1.1.概述
备忘录是一个非常容易理解的模式,只需要看一下后面的客户端测试代码就能明白备忘录模式的用意。备忘录模式就是用来保存对象的某一个状态,这样在一定时候,可以通过备忘录来恢复对象的状态。
针对上面所说的用意,实现起来就需要一个类来保存这个状态,这个类就是备忘录类。鉴于备忘录很容易理解,就直接看后面的代码实现吧。
1.1.2.代码实现
在代码实现中,备忘录模式一共有三个类,Originator类是原始的对象,正是这个对象的状态需要备忘,这样是为了在一个时间,这个对象可以恢复到备忘的状态。还有一个类是Memento,这个是备忘录,用于保存Originator类的状态。还有一个类CareTaker用来管理备忘录。
首先看Originator的代码实现。
/// <summary>
/// 用于创建备忘录的原发器,记录当前的内部状态
/// </summary>
public class Originator
{
/// <summary>
/// 当前的状态
/// </summary>
private string state;
/// <summary>
/// 创建一个当前状态的备忘录
/// </summary>
/// <returns></returns>
public Memento CreateMemento()
{
return new Memento(this.state);
}
/// <summary>
/// 恢复当前状态为备忘录所保存的状态
/// </summary>
/// <param name="memento"></param>
public void RestoreMemento(Memento memento)
{
this.state = memento.GetState();
}
public string GetState()
{
return this.state;
}
public void SetState(string state)
{
this.state = state;
}
}
Originator类具有一个状态属性,这个属性代表了Originator对象的实时状态,CreateMemento()方法用来保存Originator对象的一个状态作为备忘,在以后需要的时候可以显示。也就是说通过这个方法就可以给Originator对象产生一个备忘录。RestoreMemento()方法用来从保存的备忘录中恢复Originator的状态,将Originator的状态设置为之前的一个备忘状态。
下面是备忘录类Memento类的代码实现。
/// <summary>
/// 备忘录类
/// </summary>
public class Memento
{
/// <summary>
/// 备忘录所保存的状态
/// </summary>
private string state;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="state"></param>
public Memento(string state)
{
this.state = state;
}
public string GetState()
{
return this.state;
}
public void SetState(string state)
{
this.state = state;
}
}
因为Memento类需要保存Originator对象的状态,所以Memento具有一个Originator状态一样的属性state,这个state用来保存Originator的一个状态。
Memento类的管理者是CareTaker类,CareTaker类的代码如下。
/// <summary>
/// 负责保存备忘录的管理员
/// </summary>
public class CareTaker
{
/// <summary>
/// 备忘录
/// </summary>
private Memento memento;
/// <summary>
/// 返回所拥有的备忘录
/// </summary>
/// <returns></returns>
public Memento retrieveMemento()
{
return this.memento;
}
/// <summary>
/// 保存一个备忘录
/// </summary>
/// <param name="memento"></param>
public void SaveMemento(Memento memento)
{
this.memento = memento;
}
}
CareTaker类用来管理Memento对象,所以自身拥有一个Memento对象。并且有一个设置Memento对象方法(SaveMemento)和一个取得Memento的方法(retrieveMemento)。
备忘录模式的核心代码已经定义好了,下面通过客户端代码来测试一下备忘录模式。
private static Originator o = new Originator();
private static CareTaker c = new CareTaker();
public static void Test()
{
o.SetState("On");
c.SaveMemento(o.CreateMemento());
Console.WriteLine("第一次为" + o.GetState());
o.SetState("Off");
Console.WriteLine("第二次为" + o.GetState());
o.RestoreMemento(c.retrieveMemento());
Console.WriteLine("恢复到上一次为" + o.GetState());
}
在客户端代码中,定义了一个Originator的对象o和CareTaker 的对象c,首先对o设置了一个状态,并且通过c(c是管理备忘录的)来把o目前的状态备忘(保存为备忘录)。然后继续设置了一下o的状态,在此时,o想恢复自己的状态到之前,因为想恢复的状态保存在了备忘录中,所以通过o的RestoreMemento可以将状态恢复为备忘录所记载的状态。
客户端的测试代码演示的是备忘录模式的意图,而之前的代码演示的是备忘录模式的结构。
1.1.3.模型表示
备忘录模式的UML类图如下所示。
在备忘录模式的类模型图中可以看出来,Originator,Memento和CareTaker三者之间的关系,Memento用来保存Originator的一个状态,Caretaker用来管理Memento。在Originator需要Memento的时候,通过Caretaker即可得到。在Originator需要备忘一个状态的时候,通过Caretaker即可备忘。
1.1.4.模式分析
备忘录模式就是用来在对象的外部可以保存对象的一个内部状态。用来存储另外一个对象内部状态的快照。备忘录即对象的一个检查点,也是一个还原点。
检查点:某一个快照所处的位置(即一个状态)。
备忘录模式的意图是在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态。
通过代码实现中的客户端测试代码即可看到备忘录模式的使用场景:
1、必须保存一个对象在某一个时刻的状态,这样以后需要的时候就能恢复到之前的状态。
2、如果用接口来让其他对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。
通过使用备忘录模式,可以达到一些效果:
1、保持封装边界。应用使用备忘录模式,将Originator对象中的一些细节隔离到了Caretaker和Memento中。保持了Originator类内部信息对其他对象的隐藏,从而保持了封装边界。
2、简化了Originator类,将Originator类中的一些操作交给了Caretaker和Memento。
3、增加了额外的代价,使用了备忘录模式,就导致了Originator类中的状态在产生备忘录的时候复制了信息,这样如果需要备忘的信息量很大,那么备忘录模式会导致额外大量的开销。
4、定义窄接口和宽接口。一些语言中可能难以保证只有Originator可访问备忘录的状态。也就是说Originator之外的对象可能会访问Memento的状态,导致不安全。
5、维护备忘录存在潜在的代价。Caretaker在维护Memento的时候,因为Caretaker不知道备忘录Memento所保存的状态数量,所以在通过Caretaker来保存Memento的时候,可能会导致大量的存储开销。
因为效果4所说的潜在的安全问题,备忘录还有另外一种安全的实现方式,也就是将Memento定义为Originator的内部类,这样的话Memento也就是只有Originator才能访问了。
安全模式的一种备忘录模式代码实现,如下:
public interface IMementoSafeMode
{
}
定义一个备忘录的接口,这样就封装了备忘录的访问,外部的类都通过访问接口来访问备忘录。备忘录模式的一个要点就是不破坏封装性。
下面是一种安全实现的备忘录管理员类的实现。
public class CareTakerSafeMode
{
private IMementoSafeMode memento;
public IMementoSafeMode RetriveMemento()
{
return this.memento;
}
public void SaveMemento(IMementoSafeMode memento)
{
this.memento = memento;
}
}
通过接口的实现,管理员完全不会访问备忘录类的,这样就使得备忘录类只会暴露给和他有关系的类,这也是单一原则的体现,即迪米特法则(LOD):只与你的朋友通信,不和陌生人说话。
下面是安全的备忘录模式的核心实现。
public class OriginatorSafeMode
{
private string state;
public OriginatorSafeMode() { }
public IMementoSafeMode CreateMemento()
{
MementoSafeMode mbox = new MementoSafeMode(this.state);
return mbox;
}
public void RestoreMemento(IMementoSafeMode memento)
{
MementoSafeMode mementoSafeMode = (MementoSafeMode)memento;
this.state = mementoSafeMode.State;
}
public string State
{
get { return this.state; }
set { this.state = value; }
}
protected class MementoSafeMode : IMementoSafeMode
{
private string state;
/// <summary>
/// </summary>
/// <param name="state"></param>
public MementoSafeMode(string state)
{
this.state = state;
}
public string State
{
get { return this.state; }
set { this.state = value; }
}
}
}
在之前的备忘录模式中,备忘录模式Memento和Originator类是分开的,这里将Memento作为Originator的内部类,这样限制了Memento的访问范围,即只有Originator类的内部才能访问。
注意C#和JAVA在处理内部类的时候有不一致的地方。C#中内部类的Public属性只能是外部类范围内访问,如果是private则外部类也无法访问,只有在内部类之内才能访问。但是在java中要实现这样的效果,内部类的所有属性都必须是Private,这样才限制了只有外部类访问。在java中,外部类类似于一个命名空间,通过外部类可以看到内部类(OriginatorSafeMode.MementoSafeMode这样的写法就导致了内部类的暴露,只不过无法实例化而已)。即使不能实例化,但是也对外暴露了内部类。在C#中这一点有所改进,内部类在外面根本无法看到。
实现了内部类的UML类图省略,内部类在UML中使用圈里面包含的一个叉来显示,另一端是箭头,箭头指向的是内部类。Visual Studio 2010中自带的UML类图和Visio中的UML类图都不支持内部类的表示,使用rose可以画。
1.1.5.思想
目的:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。
实现思想:用一个Memento的类来保存需要恢复的状态,可以是一个或者多个。并且由另一个类CareTaker来负责恢复。
1.1.6.应用
应用中如果有类似历史记录机制的功能,那么这个功能有可能就能使用备忘录模式,因为历史记录就是一个过去的状态,那么如果要访问历史记录就表示要恢复状态。保存历史记录的类就是一个备忘录类。
当然了,如果理解了之前的备忘录模式的实现,那么根据其要点“恢复到原先保存的状态”,那么如果需要恢复一个类的状态到以前的某个状态,则备忘录基本上就可以搞定,但是,切忌杀鸡用牛刀的悲剧。
-----------------------------------------------------
Silence, the way to avoid many problems;
Smile, the way to solve many problems;