Refer to:http://blog.sina.com.cn/s/blog_4a2100f8010142n1.html
1.1.1.概述
備忘錄是一個(gè)非常容易理解的模式,只需要看一下后面的客戶端測試代碼就能明白備忘錄模式的用意。備忘錄模式就是用來保存對象的某一個(gè)狀態(tài),這樣在一定時(shí)候,可以通過備忘錄來恢復(fù)對象的狀態(tài)。
針對上面所說的用意,實(shí)現(xiàn)起來就需要一個(gè)類來保存這個(gè)狀態(tài),這個(gè)類就是備忘錄類。鑒于備忘錄很容易理解,就直接看后面的代碼實(shí)現(xiàn)吧。
1.1.2.代碼實(shí)現(xiàn)
在代碼實(shí)現(xiàn)中,備忘錄模式一共有三個(gè)類,Originator類是原始的對象,正是這個(gè)對象的狀態(tài)需要備忘,這樣是為了在一個(gè)時(shí)間,這個(gè)對象可以恢復(fù)到備忘的狀態(tài)。還有一個(gè)類是Memento,這個(gè)是備忘錄,用于保存Originator類的狀態(tài)。還有一個(gè)類CareTaker用來管理備忘錄。
首先看Originator的代碼實(shí)現(xiàn)。
/// <summary>
/// 用于創(chuàng)建備忘錄的原發(fā)器,記錄當(dāng)前的內(nèi)部狀態(tài)
/// </summary>
public class Originator
{
/// <summary>
/// 當(dāng)前的狀態(tài)
/// </summary>
private string state;
/// <summary>
/// 創(chuàng)建一個(gè)當(dāng)前狀態(tài)的備忘錄
/// </summary>
/// <returns></returns>
public Memento CreateMemento()
{
return new Memento(this.state);
}
/// <summary>
/// 恢復(fù)當(dāng)前狀態(tài)為備忘錄所保存的狀態(tài)
/// </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類具有一個(gè)狀態(tài)屬性,這個(gè)屬性代表了Originator對象的實(shí)時(shí)狀態(tài),CreateMemento()方法用來保存Originator對象的一個(gè)狀態(tài)作為備忘,在以后需要的時(shí)候可以顯示。也就是說通過這個(gè)方法就可以給Originator對象產(chǎn)生一個(gè)備忘錄。RestoreMemento()方法用來從保存的備忘錄中恢復(fù)Originator的狀態(tài),將Originator的狀態(tài)設(shè)置為之前的一個(gè)備忘狀態(tài)。
下面是備忘錄類Memento類的代碼實(shí)現(xiàn)。
/// <summary>
/// 備忘錄類
/// </summary>
public class Memento
{
/// <summary>
/// 備忘錄所保存的狀態(tài)
/// </summary>
private string state;
/// <summary>
/// 構(gòu)造函數(shù)
/// </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;
}
}
因?yàn)镸emento類需要保存Originator對象的狀態(tài),所以Memento具有一個(gè)Originator狀態(tài)一樣的屬性state,這個(gè)state用來保存Originator的一個(gè)狀態(tài)。
Memento類的管理者是CareTaker類,CareTaker類的代碼如下。
/// <summary>
/// 負(fù)責(zé)保存?zhèn)渫浀墓芾韱T
/// </summary>
public class CareTaker
{
/// <summary>
/// 備忘錄
/// </summary>
private Memento memento;
/// <summary>
/// 返回所擁有的備忘錄
/// </summary>
/// <returns></returns>
public Memento retrieveMemento()
{
return this.memento;
}
/// <summary>
/// 保存一個(gè)備忘錄
/// </summary>
/// <param name="memento"></param>
public void SaveMemento(Memento memento)
{
this.memento = memento;
}
}
CareTaker類用來管理Memento對象,所以自身擁有一個(gè)Memento對象。并且有一個(gè)設(shè)置Memento對象方法(SaveMemento)和一個(gè)取得Memento的方法(retrieveMemento)。
備忘錄模式的核心代碼已經(jīng)定義好了,下面通過客戶端代碼來測試一下備忘錄模式。
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("恢復(fù)到上一次為" + o.GetState());
}
在客戶端代碼中,定義了一個(gè)Originator的對象o和CareTaker 的對象c,首先對o設(shè)置了一個(gè)狀態(tài),并且通過c(c是管理備忘錄的)來把o目前的狀態(tài)備忘(保存為備忘錄)。然后繼續(xù)設(shè)置了一下o的狀態(tài),在此時(shí),o想恢復(fù)自己的狀態(tài)到之前,因?yàn)橄牖謴?fù)的狀態(tài)保存在了備忘錄中,所以通過o的RestoreMemento可以將狀態(tài)恢復(fù)為備忘錄所記載的狀態(tài)。
客戶端的測試代碼演示的是備忘錄模式的意圖,而之前的代碼演示的是備忘錄模式的結(jié)構(gòu)。
1.1.3.模型表示
備忘錄模式的UML類圖如下所示。
在備忘錄模式的類模型圖中可以看出來,Originator,Memento和CareTaker三者之間的關(guān)系,Memento用來保存Originator的一個(gè)狀態(tài),Caretaker用來管理Memento。在Originator需要Memento的時(shí)候,通過Caretaker即可得到。在Originator需要備忘一個(gè)狀態(tài)的時(shí)候,通過Caretaker即可備忘。
1.1.4.模式分析
備忘錄模式就是用來在對象的外部可以保存對象的一個(gè)內(nèi)部狀態(tài)。用來存儲另外一個(gè)對象內(nèi)部狀態(tài)的快照。備忘錄即對象的一個(gè)檢查點(diǎn),也是一個(gè)還原點(diǎn)。
檢查點(diǎn):某一個(gè)快照所處的位置(即一個(gè)狀態(tài))。
備忘錄模式的意圖是在不破壞封裝性的前提下,捕獲一個(gè)對象的內(nèi)部狀態(tài),并在該對象之外保存這個(gè)狀態(tài),這樣以后就可以將該對象恢復(fù)到原先保存的狀態(tài)。
通過代碼實(shí)現(xiàn)中的客戶端測試代碼即可看到備忘錄模式的使用場景:
1、必須保存一個(gè)對象在某一個(gè)時(shí)刻的狀態(tài),這樣以后需要的時(shí)候就能恢復(fù)到之前的狀態(tài)。
2、如果用接口來讓其他對象直接得到這些狀態(tài),將會暴露對象的實(shí)現(xiàn)細(xì)節(jié)并破壞對象的封裝性。
通過使用備忘錄模式,可以達(dá)到一些效果:
1、保持封裝邊界。應(yīng)用使用備忘錄模式,將Originator對象中的一些細(xì)節(jié)隔離到了Caretaker和Memento中。保持了Originator類內(nèi)部信息對其他對象的隱藏,從而保持了封裝邊界。
2、簡化了Originator類,將Originator類中的一些操作交給了Caretaker和Memento。
3、增加了額外的代價(jià),使用了備忘錄模式,就導(dǎo)致了Originator類中的狀態(tài)在產(chǎn)生備忘錄的時(shí)候復(fù)制了信息,這樣如果需要備忘的信息量很大,那么備忘錄模式會導(dǎo)致額外大量的開銷。
4、定義窄接口和寬接口。一些語言中可能難以保證只有Originator可訪問備忘錄的狀態(tài)。也就是說Originator之外的對象可能會訪問Memento的狀態(tài),導(dǎo)致不安全。
5、維護(hù)備忘錄存在潛在的代價(jià)。Caretaker在維護(hù)Memento的時(shí)候,因?yàn)镃aretaker不知道備忘錄Memento所保存的狀態(tài)數(shù)量,所以在通過Caretaker來保存Memento的時(shí)候,可能會導(dǎo)致大量的存儲開銷。
因?yàn)樾Ч?所說的潛在的安全問題,備忘錄還有另外一種安全的實(shí)現(xiàn)方式,也就是將Memento定義為Originator的內(nèi)部類,這樣的話Memento也就是只有Originator才能訪問了。
安全模式的一種備忘錄模式代碼實(shí)現(xiàn),如下:
public interface IMementoSafeMode
{
}
定義一個(gè)備忘錄的接口,這樣就封裝了備忘錄的訪問,外部的類都通過訪問接口來訪問備忘錄。備忘錄模式的一個(gè)要點(diǎn)就是不破壞封裝性。
下面是一種安全實(shí)現(xiàn)的備忘錄管理員類的實(shí)現(xiàn)。
public class CareTakerSafeMode
{
private IMementoSafeMode memento;
public IMementoSafeMode RetriveMemento()
{
return this.memento;
}
public void SaveMemento(IMementoSafeMode memento)
{
this.memento = memento;
}
}
通過接口的實(shí)現(xiàn),管理員完全不會訪問備忘錄類的,這樣就使得備忘錄類只會暴露給和他有關(guān)系的類,這也是單一原則的體現(xiàn),即迪米特法則(LOD):只與你的朋友通信,不和陌生人說話。
下面是安全的備忘錄模式的核心實(shí)現(xiàn)。
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的內(nèi)部類,這樣限制了Memento的訪問范圍,即只有Originator類的內(nèi)部才能訪問。
注意C#和JAVA在處理內(nèi)部類的時(shí)候有不一致的地方。C#中內(nèi)部類的Public屬性只能是外部類范圍內(nèi)訪問,如果是private則外部類也無法訪問,只有在內(nèi)部類之內(nèi)才能訪問。但是在java中要實(shí)現(xiàn)這樣的效果,內(nèi)部類的所有屬性都必須是Private,這樣才限制了只有外部類訪問。在java中,外部類類似于一個(gè)命名空間,通過外部類可以看到內(nèi)部類(OriginatorSafeMode.MementoSafeMode這樣的寫法就導(dǎo)致了內(nèi)部類的暴露,只不過無法實(shí)例化而已)。即使不能實(shí)例化,但是也對外暴露了內(nèi)部類。在C#中這一點(diǎn)有所改進(jìn),內(nèi)部類在外面根本無法看到。
實(shí)現(xiàn)了內(nèi)部類的UML類圖省略,內(nèi)部類在UML中使用圈里面包含的一個(gè)叉來顯示,另一端是箭頭,箭頭指向的是內(nèi)部類。Visual Studio 2010中自帶的UML類圖和Visio中的UML類圖都不支持內(nèi)部類的表示,使用rose可以畫。
1.1.5.思想
目的:在不破壞封裝性的前提下,捕獲一個(gè)對象的內(nèi)部狀態(tài),并在該對象之外保存這個(gè)狀態(tài)。這樣以后就可以將該對象恢復(fù)到原先保存的狀態(tài)。
實(shí)現(xiàn)思想:用一個(gè)Memento的類來保存需要恢復(fù)的狀態(tài),可以是一個(gè)或者多個(gè)。并且由另一個(gè)類CareTaker來負(fù)責(zé)恢復(fù)。
1.1.6.應(yīng)用
應(yīng)用中如果有類似歷史記錄機(jī)制的功能,那么這個(gè)功能有可能就能使用備忘錄模式,因?yàn)闅v史記錄就是一個(gè)過去的狀態(tài),那么如果要訪問歷史記錄就表示要恢復(fù)狀態(tài)。保存歷史記錄的類就是一個(gè)備忘錄類。
當(dāng)然了,如果理解了之前的備忘錄模式的實(shí)現(xiàn),那么根據(jù)其要點(diǎn)“恢復(fù)到原先保存的狀態(tài)”,那么如果需要恢復(fù)一個(gè)類的狀態(tài)到以前的某個(gè)狀態(tài),則備忘錄基本上就可以搞定,但是,切忌殺雞用牛刀的悲劇。
-----------------------------------------------------
Silence, the way to avoid many problems;
Smile, the way to solve many problems;