關於 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
- 不違反封裝的情況下,取得物件的內部狀態,以便之後可以將物件還原到先前狀態
Memento(備忘錄)屬於行為型(Behavioral Patterns),當遇到需要狀態回到某個狀態時,可藉由 Memento 來將物件備份,包含物件中 private 的屬性都可以透過 Memento 來記錄,也可以將每次異動物件都記錄至 Memento 來達到上一步、下一步的功能。
優點:
- 將儲存狀態的邏輯放在 Memento 使原本物件職責維持單一
缺點:
- 過多的紀錄會大量消耗記憶體
2. UML
Class 間關聯:
- Originator 依賴 Memento
- Caretaker 可包含 Memento
Class:
- Originator:內部狀態需要保存的物件
- Memento:負責保存 Originator 的狀態
- Caretaker:存放 Memento 但不操作
3. 將 UML 轉為程式碼
內部狀態需要保存的物件
/// <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 的狀態
/// <summary>
/// 負責保存 Originator 的狀態
/// </summary>
public class Memento
{
public string State { get; }
public Memento(string state)
{
State = state;
}
}
存放 Memento 但不操作
/// <summary>
/// 存放 Memento 但不操作
/// </summary>
public class Caretaker
{
public Memento Memento { set; get; }
}
- 建立
originator
並給定初始值 On - 建立
caretaker
並透過originator
建立Memento
- 改變
State
的值 - 透過
Memento
還原State
的值
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();
}
執行結果
State = On
State = Off
Restoring state...
State = On
4. 情境
我們接到了一個門市結帳刷條碼要能夠「上一步」的需求
- 每次新增商品會增加一份商品快照
- 點選「上一步」時會還原至上一份快照
結帳刷條碼
/// <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>
保存每次刷條碼新增商品的快照
/// <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 但不操作
/// <summary>
/// 存放 Memento 但不操作
/// </summary>
public class Caretaker
{
public Memento Memento { set; get; }
}
- 建立
scanTheBarcode
- 建立
caretaker
並透過scanTheBarcode
建立Memento
- 新增商品
- 透過
PreviousStep
移除前一項商品
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();
}
執行結果
Products = 麵包
Products = 麵包, 蘋果
Products = 麵包, 蘋果, 餅乾
Products = 麵包, 蘋果, 餅乾, 蛋糕切片
== 上一步 ==
Products = 麵包, 蘋果, 餅乾
Products = 麵包, 蘋果, 餅乾, 整塊蛋糕
完整程式碼
GitHub:Behavioral_06_Memento