C#設計模式-備忘錄模式

訪問者模式的實現是把做用於某種數據結構上的操做封裝到訪問者中,使得操做和數據結構隔離。而本文要介紹的備忘者模式與命令模式有點類似,不一樣的是,命令模式保存的是發起人的具體命令(命令對應的是行爲),而備忘錄模式保存的是發起人的狀態(而狀態對應的數據結構,如屬性)。下面具體來看看備忘錄模式。數據庫

1、 備忘錄(Memento)模式數據結構

從字面意思就能夠明白,備忘錄模式就是對某個類的狀態進行保存下來,等到須要恢復的時候,能夠從備忘錄中進行恢復。生活中這樣的例子常常看到,如備忘電話通信錄,備份操做操做系統,備份數據庫等。編輯器

備忘錄模式的具體定義是:在不破壞封裝的前提下,捕獲一個對象的內部狀態,並在該對象以外保存這個狀態,這樣之後就能夠把該對象恢復到原先的狀態。this

2、 備忘錄模式的結構spa

介紹完備忘錄模式的定義以後,下面具體看看備忘錄模式的結構圖:操作系統

備忘錄模式中主要有三類角色:code

  • 發起人角色:記錄當前時刻的內部狀態,負責建立和恢復備忘錄數據。
  • 備忘錄角色:負責存儲發起人對象的內部狀態,在進行恢復時提供給發起人須要的狀態。
  • 管理者角色:負責保存備忘錄對象。

3、 備忘錄模式的實現對象

下面以備份手機通信錄爲例子來實現了備忘錄模式,具體的實現代碼以下所示:blog

// 聯繫人

using System;
using System.Collections.Generic;

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)
    {
        var persons = new List<ContactPerson>
        {
                new ContactPerson { Name= "Learning Hard", MobileNum = "123445"},
                new ContactPerson { Name = "Tony", MobileNum = "234565"},
                new ContactPerson { Name = "Jock", MobileNum = "231455"}
            };
        var mobileOwner = new MobileOwner(persons);
        mobileOwner.Show();

        // 建立備忘錄並保存備忘錄對象
        var caretaker = new Caretaker {ContactM = mobileOwner.CreateMemento()};

        // 更改發起人聯繫人列表
        Console.WriteLine("----移除最後一個聯繫人--------");
        mobileOwner.ContactPersons.RemoveAt(2);
        mobileOwner.Show();

        // 恢復到原始狀態
        Console.WriteLine("-------恢復聯繫人列表------");
        mobileOwner.RestoreMemento(caretaker.ContactM);
        mobileOwner.Show();

        Console.Read();
    }
}

具體的運行結果以下所示:索引

聯繫人列表中有3我的,他們是:
姓名:Learning Hard 號碼爲:123445
姓名:Tony 號碼爲:234565
姓名:Jock 號碼爲:231455
----移除最後一個聯繫人--------
聯繫人列表中有2我的,他們是:
姓名:Learning Hard 號碼爲:123445
姓名:Tony 號碼爲:234565
-------恢復聯繫人列表------
聯繫人列表中有3我的,他們是:
姓名:Learning Hard 號碼爲:123445
姓名:Tony 號碼爲:234565
姓名:Jock 號碼爲:231455

能夠看出,剛開始通信錄中有3個聯繫人,而後移除之後一個後變成2個聯繫人了,最後恢復原來的聯繫人列表後,聯繫人列表中又恢復爲3個聯繫人了。

上面代碼只是保存了一個還原點,即備忘錄中只保存了3個聯繫人的數據,可是,若是想備份多個還原點怎麼辦呢?即恢復到3我的後,又想恢復到前面2我的的狀態,這時候可能你會想,這樣不必啊,到時候在刪除不就行了。可是若是在實際應用中,可能咱們發了不少時間去建立通信錄中只有2個聯繫人的狀態,恢復到3我的的狀態後,發現這個狀態時錯誤的,仍是原來2我的的狀態是正確的,難道咱們又去花以前的那麼多時間去重複操做嗎?這顯然不合理,若是就思考,能不能保存多個還原點呢?保存多個還原點其實很簡單,只須要保存多個備忘錄對象就能夠了。具體實現代碼以下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

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)
        {
            var persons = new List<ContactPerson>
            {
                new ContactPerson { Name= "Learning Hard", MobileNum = "123445"},
                new ContactPerson { Name = "Tony", MobileNum = "234565"},
                new ContactPerson { Name = "Jock", MobileNum = "231455"}
            };

            var mobileOwner = new MobileOwner(persons);
            mobileOwner.Show();

            // 建立備忘錄並保存備忘錄對象
            var 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("輸入的索引大於集合長度!");
                }
            }
        }
    }
}

這樣就保存了多個狀態,客戶端能夠選擇恢復的狀態點,具體運行結果以下所示:

聯繫人列表中有3我的,他們是:
姓名:Learning Hard 號碼爲:123445
姓名:Tony 號碼爲:234565
姓名:Jock 號碼爲:231455
----移除最後一個聯繫人--------
聯繫人列表中有2我的,他們是:
姓名:Learning Hard 號碼爲:123445
姓名:Tony 號碼爲:234565
-------恢復聯繫人列表,請從如下列表選擇恢復的日期------
Key = 2016/12/19 18:06:09
Key = 2016/12/19 18:06:10
請輸入數字,按窗口的關閉鍵退出:0
聯繫人列表中有3我的,他們是:
姓名:Learning Hard 號碼爲:123445
姓名:Tony 號碼爲:234565
姓名:Jock 號碼爲:231455
請輸入數字,按窗口的關閉鍵退出:1
聯繫人列表中有2我的,他們是:
姓名:Learning Hard 號碼爲:123445
姓名:Tony 號碼爲:234565
請輸入數字,按窗口的關閉鍵退出:

4、 備忘錄模式的適用場景

在如下狀況下能夠考慮使用備忘錄模式:

  1. 若是系統須要提供回滾操做時,使用備忘錄模式很是合適。例如文本編輯器的Ctrl+Z撤銷操做的實現,數據庫中事務操做。

5、 備忘錄模式的優缺點

優勢:

  1. 若是某個操做錯誤地破壞了數據的完整性,此時能夠使用備忘錄模式將數據恢復成原來正確的數據。
  2. 備份的狀態數據保存在發起人角色以外,這樣發起人就不須要對各個備份的狀態進行管理。而是由備忘錄角色進行管理,而備忘錄角色又是由管理者角色管理,符合單一職責原則。

缺點:

  1. 在實際的系統中,可能須要維護多個備份,須要額外的資源,這樣對資源的消耗比較嚴重。

6、 總結

備忘錄模式主要思想是——利用備忘錄對象來對保存發起人的內部狀態,當發起人須要恢復原來狀態時,再從備忘錄對象中進行獲取,在實際開發過程也應用到這點,例如數據庫中的事務處理。

相關文章
相關標籤/搜索