1. 程式人生 > >通俗易懂設計模式解析——備忘錄模式

通俗易懂設計模式解析——備忘錄模式

前言

  今天我們來看看備忘錄模式【MementoPattern】,我們平時寫文件的時候一不小心寫錯了一些字或者刪除了一些東西怎麼辦呢?不用怕、Windows裡面提供了Ctrl+Z,後退一步,可以一直後退。這個東西怎麼實現的呢?我們記得之前講過一個命令模式。命令儲存的是發起人的具體命令(對應的行為)、我們今天講的這個備忘錄跟這個有點相似,但是備忘錄模式儲存的是發起人的狀態(對應的資料結構、如屬性)。我們沒做一步操作就儲存一步操作之前的資料。當我們Ctrl+Z後退時恢復前一步資料、似乎就達到了我們需要的目的。

備忘錄模式介紹

一、來由

  在軟體系統中我們經常會遇到一些狀態的轉變。在某些時刻我們需要恢復、回溯之前的某個時間點的狀態。如果我們使用一個公共的介面來使其他物件得到獲取這個物件、會暴露物件封裝的細節。那麼我們如何在不破壞物件的封裝性的同時恢復物件的某一時刻的狀態呢?

二、意圖

  在不破壞封裝性的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態。

三、案例圖

 

四、備忘錄模式程式碼示例

我們看上面的案例圖,主要包含以下三個部分:

發起人:發起人角色負責對狀態的記錄,包含建立和恢復備忘資料。

備忘錄:負責儲存發起人物件的狀態、在恢復備忘資料的時候提供發起人需要的狀態。

管理員:負責儲存備忘錄物件、負責備忘錄物件、使其不能被其他物件進行訪問及操作。

接下來我們看一個案例、關於手機照片備份的問題,有些時候因為操作失誤引起的資料遺失問題。怎麼去避免呢?對照片備份,然後在需要的時候進行備份資料恢復。我們看下如歌通過程式碼來實現吧:

 

namespace Memento_Pattern
{
    class MementoPattern
    {
    }
    #region 照片資料

    public class Photo 
    {
        /// <summary>
        /// 名稱
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 地址
        /// </summary>
        public string Address { get; set; }
    }
    #endregion

    #region  發起人角色

    public sealed class Sponsor 
    {
        private List<Photo> _photo;
        public Sponsor(List<Photo> photos) 
        {
            if (photos == null)
                throw new Exception("請傳入正確的資料來源!");
            this._photo = photos;
        }
        public List<Photo> GetPhotos
        {
            get { return this._photo; }
            set { this._photo = value; }
        }

        /// <summary>
        /// 建立備忘錄,儲存狀態資料
        /// </summary>
        /// <returns></returns>
        public Memento CreateMemento() 
        {
            return new Memento(new List<Photo>(this._photo));
        }

        /// <summary>
        /// 獲取備忘錄資料、恢復狀態資料
        /// </summary>
        /// <param name="memento"></param>
        public void RestoreMemento(Memento memento) 
        {
            GetPhotos = memento._mementoList;
        }

        /// <summary>
        /// 展示資料
        /// </summary>
        public void ShowPhoto() 
        {
            Console.WriteLine($"目前用有照片{GetPhotos.Count}張:");
            foreach (var item in GetPhotos)
            {
                Console.WriteLine($"照片名稱:{item.Name}。照片地址:{item.Address}");
            }
        }
    }
    #endregion

    #region   備忘錄
    public sealed class Memento 
    {
        public List<Photo> _mementoList { get; private set; }

        /// <summary>
        /// 初始化儲存資料
        /// </summary>
        /// <param name="MementoList"></param>
        public Memento(List<Photo> MementoList)
        {
            this._mementoList = MementoList;
        }

    }
    #endregion

    #region  管理員
    /// <summary>
    /// 一個備忘錄資料處理
    /// </summary>
    public sealed class MementoManager 
    {
        public Memento  memento { get; set; }
    }
  #endregion
}

 

 

namespace Memento_Pattern
{
    class Program
    {

        static void Main(string[] args)
        {
            ///初始化資料
            List<Photo> photos = new List<Photo>();
            photos.Add(new Photo { Name = "第一張.jpg", Address = "https://img2018.cnblogs.com/blog/1470432/201910/11.jpg" });
            photos.Add(new Photo { Name = "第二張.jpg", Address = "https://img2018.cnblogs.com/blog/1470432/201910/22.jpg" });
            photos.Add(new Photo { Name = "第三張.jpg", Address = "https://img2018.cnblogs.com/blog/1470432/201910/33.jpg" });
            Sponsor sponsor = new Sponsor(photos);

            ///展示資料
            sponsor.ShowPhoto();

            ///儲存狀態資料到備忘錄
            MementoManager mementoManager = new MementoManager();
            mementoManager.memento = sponsor.CreateMemento();

            ///刪除一張照片
            Console.WriteLine();
            Console.WriteLine();
            photos.RemoveAt(0);
            sponsor.GetPhotos = photos;
            Console.WriteLine("刪除後");
            sponsor.ShowPhoto();

            ///恢復備忘錄資料
            ///
            Console.WriteLine();
            Console.WriteLine();
            sponsor.RestoreMemento(mementoManager.memento);
            Console.WriteLine("恢復後");
            sponsor.ShowPhoto();

        }
    }
}

  這裡我們可以看到 對照片的備份、然後刪除之後完成恢復操作。這裡針對的是一個備忘錄的操作。

 

  我們看下如果我們使用備忘錄進行多次狀態的儲存並且選擇性恢復資料是如何實現的吧。

  首先對管理員角色進行修改:

    /// <summary>
    /// 多個備忘錄資料處理
    /// </summary>
    public sealed class MementoManagers
    {
        public Dictionary<string, Memento> mementoList { get; set; }
        public MementoManagers() 
        {
            mementoList = new Dictionary<string, Memento>();
        }
    }

  然後我們修改Main函式進行操作看下結果

static void Main(string[] args)
        {
            ///初始化資料
            List<Photo> photos = new List<Photo>();
            photos.Add(new Photo { Name = "第一張.jpg", Address = "https://img2018.cnblogs.com/blog/1470432/201910/11.jpg" });
            photos.Add(new Photo { Name = "第二張.jpg", Address = "https://img2018.cnblogs.com/blog/1470432/201910/22.jpg" });
            photos.Add(new Photo { Name = "第三張.jpg", Address = "https://img2018.cnblogs.com/blog/1470432/201910/33.jpg" });
            Sponsor sponsor = new Sponsor(photos);

            ///展示資料
            sponsor.ShowPhoto();

            ///儲存狀態資料到備忘錄
            MementoManagers mementoManagers = new MementoManagers();
            mementoManagers.mementoList.Add("1", sponsor.CreateMemento()); 

            ///刪除一張照片
            Console.WriteLine();
            Console.WriteLine();
            photos.RemoveAt(0);
            sponsor.GetPhotos = photos;
            Console.WriteLine("刪除後");
            sponsor.ShowPhoto();
            mementoManagers.mementoList.Add("2", sponsor.CreateMemento());

            ///恢復備忘錄資料
            ///
            while (true)
            {
                Console.WriteLine();
                Console.WriteLine();
                Console.WriteLine($"目前有{mementoManagers.mementoList.Count}個備份資料,請輸入序號選擇備份資料恢復");
                var index = Console.ReadLine();
                sponsor.RestoreMemento(mementoManagers.mementoList.GetValueOrDefault(index));
                Console.WriteLine("恢復後");
                sponsor.ShowPhoto();
                Console.WriteLine("輸入q退出");
                var q = Console.ReadLine();
                if (q=="q")
                {
                    break;
                }
            }
           

        }

 

使用場景及優缺點

一、使用場景

1、需要儲存/恢復資料的場景可以使用備忘錄模式。

2、可提供回滾操作的場景可使用備忘錄模式、例如Ctrl+Z。

二、優點

1、給使用者提供了一個恢復機制,可以回退到某個歷史狀態。

2、備忘錄的狀態由備忘錄角色管理,備忘錄由管理角色管理,備份資料和恢復資料由發起人管理。符合單一職責原則。

三、缺點

1、會消耗大量的記憶體,儲存一次消耗一次。最終都會消耗較多記憶體。

總結

  到這裡我們就介紹完了備忘錄模式。備忘錄模式將物件的狀態資料進行儲存,儲存在備忘錄角色中。然後通過管理員角色進行管理。可以將物件回退到歷史某一時刻的狀態資料。在遊戲中的存檔可使用此模式、Ctrl+Z回退可使用此模式、還有瀏覽器回退歷史、資料庫事務管理。關於回退操作都可以使用此模式進行操作。這裡我們需要注意的是與命令模式進行區分。備忘錄模式儲存的是物件的狀態資料。命令模式儲存的是物件發起的命令也就是行為。備忘錄模式是對行為狀態的操作、命令模式是對行為序列的操作。


  用愛生活,你會使自己幸福!用愛工作,你會使很多人幸福!

   C#設計模式系列目錄

     歡迎大家掃描下方二維碼,和我一起踏上設計模式的闖關之路吧!

 &n