# 19. Memento ###### tags: `DesignPatterns` ## 關於 Memento 本篇將討論以下幾個問題 > ### 1. 關於 Memento > ### 2. UML > ### 3. 將 UML 轉為程式碼 > ### 4. 情境 --- ## 測試環境: >OS:Windows 10 >IDE:Visual Studio 2019 --- ## 1. 關於 Memento > Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later. > > by [Gang of Four](https://en.wikipedia.org/wiki/Design_Patterns) - 不違反封裝的情況下,取得物件的內部狀態,以便之後可以將物件還原到先前狀態 Memento(備忘錄)屬於行為型(Behavioral Patterns),當遇到需要**狀態回到某個狀態**時,可藉由 Memento 來將物件備份,包含物件中 private 的屬性都可以透過 Memento 來記錄,也可以將每次異動物件都記錄至 Memento 來達到上一步、下一步的功能。 優點: - 將儲存狀態的邏輯放在 Memento 使原本物件職責維持單一 缺點: - 過多的紀錄會大量消耗記憶體 --- ## 2. UML ![](https://i.imgur.com/ASQ5uPk.jpg) Class 間關聯: - Originator 依賴 Memento - Caretaker 可包含 Memento Class: - Originator:內部狀態需要保存的物件 - Memento:負責保存 Originator 的狀態 - Caretaker:存放 Memento 但不操作 --- ## 3. 將 UML 轉為程式碼 內部狀態需要保存的物件 ```C# /// <summary> /// 內部狀態需要保存的物件 /// </summary> public class Originator { private string _state; public string State { get => _state; set { _state = value; Console.WriteLine($"State = {_state}"); } } public Memento CreateMemento() { return (new Memento(_state)); } public void SetMemento(Memento memento) { Console.WriteLine("Restoring state..."); State = memento.State; } } ``` 負責保存 Originator 的狀態 ```C# /// <summary> /// 負責保存 Originator 的狀態 /// </summary> public class Memento { public string State { get; } public Memento(string state) { State = state; } } ``` 存放 Memento 但不操作 ```C# /// <summary> /// 存放 Memento 但不操作 /// </summary> public class Caretaker { public Memento Memento { set; get; } } ``` 1. 建立`originator`並給定初始值 On 2. 建立`caretaker`並透過`originator`建立`Memento` 3. 改變`State`的值 4. 透過`Memento`還原`State`的值 ```C# static void Main(string[] args) { Default.Originator originator = new Default.Originator { State = "On" }; Default.Caretaker caretaker = new Default.Caretaker { Memento = originator.CreateMemento() }; originator.State = "Off"; originator.SetMemento(caretaker.Memento); Console.ReadLine(); } ``` 執行結果 ```Console State = On State = Off Restoring state... State = On ``` --- ## 4. 情境 我們接到了一個門市結帳刷條碼要能夠「上一步」的需求 - 每次新增商品會增加一份商品快照 - 點選「上一步」時會還原至上一份快照 結帳刷條碼 ```C# /// <summary> /// 結帳刷條碼 /// </summary> public class ScanTheBarcode { private string _products; public void AddProducts(Memento memento, string product) { if (string.IsNullOrWhiteSpace(_products)) { _products = product; } else { _products += ", " + product; } memento.SetState(_products); Console.WriteLine($"Products = {_products}"); } public Memento CreateMemento() { return (new Memento()); } public void PreviousStep(Memento memento) { Console.WriteLine("\n== 上一步 =="); memento.State.Pop(); _products = memento.State.Pop(); Console.WriteLine($"Products = {_products}"); } } ``` 使用`Stack<string>`保存每次刷條碼新增商品的快照 ```C# /// <summary> /// 負責保存 ScanTheBarcode 的狀態 /// </summary> public class Memento { public Stack<string> State { get; } public Memento() { State = new Stack<string>(); } public void SetState(string state) { State.Push(state); } } ``` 存放 Memento 但不操作 ```C# /// <summary> /// 存放 Memento 但不操作 /// </summary> public class Caretaker { public Memento Memento { set; get; } } ``` 1. 建立`scanTheBarcode` 2. 建立`caretaker`並透過`scanTheBarcode`建立`Memento` 3. 新增商品 4. 透過`PreviousStep`移除前一項商品 ```C# static void Main(string[] args) { Situation.ScanTheBarcode scanTheBarcode = new Situation.ScanTheBarcode(); Situation.Caretaker caretaker = new Situation.Caretaker { Memento = scanTheBarcode.CreateMemento() }; scanTheBarcode.AddProducts(caretaker.Memento, "麵包"); scanTheBarcode.AddProducts(caretaker.Memento, "蘋果"); scanTheBarcode.AddProducts(caretaker.Memento, "餅乾"); scanTheBarcode.AddProducts(caretaker.Memento, "蛋糕切片"); scanTheBarcode.PreviousStep(caretaker.Memento); scanTheBarcode.AddProducts(caretaker.Memento, "整塊蛋糕"); Console.ReadLine(); } ``` 執行結果 ```Console Products = 麵包 Products = 麵包, 蘋果 Products = 麵包, 蘋果, 餅乾 Products = 麵包, 蘋果, 餅乾, 蛋糕切片 == 上一步 == Products = 麵包, 蘋果, 餅乾 Products = 麵包, 蘋果, 餅乾, 整塊蛋糕 ``` --- ## 完整程式碼 GitHub:[Behavioral_06_Memento](https://github.com/darionnnnnn/blog/tree/master/Blog/Behavioral_06_Memento) --- ## 總結 ### Memento 若是每次都將整個物件備份會大量消耗記憶體,而折衷的辦法可以在較短的時間內將每次物件異動的差異備份下來,每隔一段時間再將整個物件備份,來達到節省記憶體的目的,而時間長短則依據需求而定。 --- ## 參考資料 1. [Design Patterns](https://en.wikipedia.org/wiki/Design_Patterns) 2. [大話設計模式](https://www.tenlong.com.tw/products/9789866761799) 2. [dofactory](https://www.dofactory.com/) 3. [Refactoring.Guru](https://refactoring.guru/) --- ## 新手上路,若有錯誤還請告知,謝謝