在上一篇博文分享了訪問者模式,訪問者模式的實現是把作用于某種數據結構上的操作封裝到訪問者中,使得操作和數據結構隔離。而今天要介紹的備忘者模式與命令模式有點相似,不同的是,命令模式保存的是發起人的具體命令(命令對應的是行為),而備忘錄模式保存的是發起人的狀态(而狀态對應的數據結構,如屬性)。下面具體來看看備忘錄模式。
二、備忘錄模式介紹2.1 備忘錄模式的定義從字面意思就可以明白,備忘錄模式就是對某個類的狀态進行保存下來,等到需要恢複的時候,可以從備忘錄中進行恢複。生活中這樣的例子經常看到,如備忘電話通訊錄,備份操作操作系統,備份數據庫等。
備忘錄模式的具體定義是:在不破壞封裝的前提下,捕獲一個對象的内部狀态,并在該對象之外保存這個狀态,這樣以後就可以把該對象恢複到原先的狀态。
2.2 備忘錄模式的結構圖介紹完備忘錄模式的定義之後,下面具體看看備忘錄模式的結構圖:
備忘錄模式中主要有三類角色:
下面以備份手機通訊錄為例子來實現了備忘錄模式,具體的實現代碼如下所示:
// 聯系人
public class ContactPerson
{
public string Name { get; set; }
public string MobileNum { get; set; }
}
// 發起人
public class MobileOwner
{
// 發起人需要保存的内部狀态
public List<ContactPerson> ContactPersons { get; set; }
public MobileOwner(List<ContactPerson> persons)
{
ContactPersons = persons;
}
// 創建備忘錄,将當期要保存的聯系人列表導入到備忘錄中
public ContactMemento CreateMemento()
{
// 這裡也應該傳遞深拷貝,new List方式傳遞的是淺拷貝,
// 因為ContactPerson類中都是string類型,所以這裡new list方式對ContactPerson對象執行了深拷貝
// 如果ContactPerson包括非string的引用類型就會有問題,所以這裡也應該用序列化傳遞深拷貝
return new ContactMemento(new List<ContactPerson>(this.ContactPersons));
}
// 将備忘錄中的數據備份導入到聯系人列表中
public void RestoreMemento(ContactMemento memento)
{
// 下面這種方式是錯誤的,因為這樣傳遞的是引用,
// 則删除一次可以恢複,但恢複之後再删除的話就恢複不了.
// 所以應該傳遞contactPersonBack的深拷貝,深拷貝可以使用序列化來完成
this.ContactPersons = memento.contactPersonBack;
}
public void Show()
{
Console.WriteLine("聯系人列表中有{0}個人,他們是:", ContactPersons.Count);
foreach (ContactPerson p in ContactPersons)
{
Console.WriteLine("姓名: {0} 号碼為: {1}", p.Name, p.MobileNum);
}
}
}
// 備忘錄
public class ContactMemento
{
// 保存發起人的内部狀态
public List<ContactPerson> contactPersonBack;
public ContactMemento(List<ContactPerson> persons)
{
contactPersonBack = persons;
}
}
// 管理角色
public class Caretaker
{
public ContactMemento ContactM { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<ContactPerson> persons = new List<ContactPerson>()
{
new ContactPerson() { Name= "Learning Hard", MobileNum = "123445"},
new ContactPerson() { Name = "Tony", MobileNum = "234565"},
new ContactPerson() { Name = "Jock", MobileNum = "231455"}
};
MobileOwner mobileOwner = new MobileOwner(persons);
mobileOwner.Show();
// 創建備忘錄并保存備忘錄對象
Caretaker caretaker = new Caretaker();
caretaker.ContactM = mobileOwner.CreateMemento();
// 更改發起人聯系人列表
Console.WriteLine("----移除最後一個聯系人--------");
mobileOwner.ContactPersons.RemoveAt(2);
mobileOwner.Show();
// 恢複到原始狀态
Console.WriteLine("-------恢複聯系人列表------");
mobileOwner.RestoreMemento(caretaker.ContactM);
mobileOwner.Show();
Console.Read();
}
}
具體的運行結果如下圖所示:
從上圖可以看出,剛開始通訊錄中有3個聯系人,然後移除以後一個後變成2個聯系人了,最後恢複原來的聯系人列表後,聯系人列表中又恢複為3個聯系人了。
上面代碼隻是保存了一個還原點,即備忘錄中隻保存了3個聯系人的數據,但是,如果想備份多個還原點怎麼辦呢?即恢複到3個人後,又想恢複到前面2個人的狀态,這時候可能你會想,這樣沒必要啊,到時候再删除不就好了。但是如果在實際應用中,可能我們花了很多時間去創建通訊錄中隻有2個聯系人的狀态,恢複到3個人的狀态後,發現這個狀态時錯誤的,還是原來2個人的狀态是正确的,難道我們又去花之前的那麼多時間去重複操作嗎?這顯然不合理,如果就思考,能不能保存多個還原點呢?保存多個還原點其實很簡單,隻需要保存多個備忘錄對象就可以了。具體實現代碼如下所示:
namespace MultipleMementoPattern
{
// 聯系人
public class ContactPerson
{
public string Name { get; set; }
public string MobileNum { get; set; }
}
// 發起人
public class MobileOwner
{
public List<ContactPerson> ContactPersons { get; set; }
public MobileOwner(List<ContactPerson> persons)
{
ContactPersons = persons;
}
// 創建備忘錄,将當期要保存的聯系人列表導入到備忘錄中
public ContactMemento CreateMemento()
{
// 這裡也應該傳遞深拷貝,new List方式傳遞的是淺拷貝,
// 因為ContactPerson類中都是string類型,所以這裡new list方式對ContactPerson對象執行了深拷貝
// 如果ContactPerson包括非string的引用類型就會有問題,所以這裡也應該用序列化傳遞深拷貝
return new ContactMemento(new List<ContactPerson>(this.ContactPersons));
}
// 将備忘錄中的數據備份導入到聯系人列表中
public void RestoreMemento(ContactMemento memento)
{
if (memento != null)
{
// 下面這種方式是錯誤的,因為這樣傳遞的是引用,
// 則删除一次可以恢複,但恢複之後再删除的話就恢複不了.
// 所以應該傳遞contactPersonBack的深拷貝,深拷貝可以使用序列化來完成
this.ContactPersons = memento.ContactPersonBack;
}
}
public void Show()
{
Console.WriteLine("聯系人列表中有{0}個人,他們是:", ContactPersons.Count);
foreach (ContactPerson p in ContactPersons)
{
Console.WriteLine("姓名: {0} 号碼為: {1}", p.Name, p.MobileNum);
}
}
}
// 備忘錄
public class ContactMemento
{
public List<ContactPerson> ContactPersonBack {get;set;}
public ContactMemento(List<ContactPerson> persons)
{
ContactPersonBack = persons;
}
}
// 管理角色
public class Caretaker
{
// 使用多個備忘錄來存儲多個備份點
public Dictionary<string, ContactMemento> ContactMementoDic { get; set; }
public Caretaker()
{
ContactMementoDic = new Dictionary<string, ContactMemento>();
}
}
class Program
{
static void Main(string[] args)
{
List<ContactPerson> persons = new List<ContactPerson>()
{
new ContactPerson() { Name= "Learning Hard", MobileNum = "123445"},
new ContactPerson() { Name = "Tony", MobileNum = "234565"},
new ContactPerson() { Name = "Jock", MobileNum = "231455"}
};
MobileOwner mobileOwner = new MobileOwner(persons);
mobileOwner.Show();
// 創建備忘錄并保存備忘錄對象
Caretaker caretaker = new Caretaker();
caretaker.ContactMementoDic.Add(DateTime.Now.ToString(), mobileOwner.CreateMemento());
// 更改發起人聯系人列表
Console.WriteLine("----移除最後一個聯系人--------");
mobileOwner.ContactPersons.RemoveAt(2);
mobileOwner.Show();
// 創建第二個備份
Thread.Sleep(1000);
caretaker.ContactMementoDic.Add(DateTime.Now.ToString(), mobileOwner.CreateMemento());
// 恢複到原始狀态
Console.WriteLine("-------恢複聯系人列表,請從以下列表選擇恢複的日期------");
var keyCollection = caretaker.ContactMementoDic.Keys;
foreach (string k in keyCollection)
{
Console.WriteLine("Key = {0}", k);
}
while (true)
{
Console.Write("請輸入數字,按窗口的關閉鍵退出:");
int index = -1;
try
{
index = Int32.Parse(Console.ReadLine());
}
catch
{
Console.WriteLine("輸入的格式錯誤");
continue;
}
ContactMemento contactMentor = null;
if (index < keyCollection.Count && caretaker.ContactMementoDic.TryGetValue(keyCollection.ElementAt(index), out contactMentor))
{
mobileOwner.RestoreMemento(contactMentor);
mobileOwner.Show();
}
else
{
Console.WriteLine("輸入的索引大于集合長度!");
}
}
}
}
}
這樣就保存了多個狀态,客戶端可以選擇恢複的狀态點,具體運行結果如下所示:
三、備忘錄模式的适用場景
在以下情況下可以考慮使用備忘錄模式:
備忘錄模式具有以下優點:
當然,備忘錄模式也存在一定的缺點:
備忘錄模式主要思想是——利用備忘錄對象來保存發起人的内部狀态,當發起人需要恢複原來狀态時,再從備忘錄對象中進行獲取,在實際開發過程也應用到這點,例如數據庫中的事務處理。
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!