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


前言

  今天我們來看看備忘錄模式【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#設計模式系列目錄

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

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM