前言
今天我們來看看備忘錄模式【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#設計模式系列目錄
歡迎大家掃描下方二維碼,和我一起踏上設計模式的闖關之路吧!

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/37794.html
標籤:設計模式
上一篇:呼叫高德API,通過輸入的地址,如省份、市、區獲取經緯度 ,通過輸入的經緯度,獲取區域詳情
下一篇:《深入淺出設計模式》簡記
