備忘錄模式
備忘錄模式是一種軟件設計模式,可以將一個物件內部私有的狀態公開。可以使用此模式的一個例子是將物件回復到其先前的狀態(撤消變更),還有的例子是版本控制,以及自定義序列化。
備忘錄模式主要由三個角色來實現:發起者、管理員和備忘錄。發起者是具有內部狀態的角色。管理員可以對發起起者做一些操作,尤其是希望能夠撤消變更的操作。竹管理員首先向發起者索要一件備忘錄。然後它執行此操作(或操作序列)。為了可以回滾到操作之前的狀態,它將備忘錄返回給發起者。備忘錄本身是一個不透明的物件(管理員不能或不應更改此物件)。使用此模式時,應注意發起者是否可能更改其他物件或資源——備忘錄模式是對單一物件進行操作。
備忘錄模式的經典範例有偽隨機數生成器(PRNG)(每個消費者(管理員)在初始化偽隨機數生成器(發起者)時,若使用相同的種子值(備忘錄),可以產生相同的亂數序列),此外,還有有限狀態機及其狀態。
概述
編輯備忘錄設計模式是著名的《設計模式:可復用物件導向軟體的基礎》所收錄的二十三個之一。此書描述了如何解決常見的設計問題,以設計出彈性且異重用的物件導向軟件(亦即更容易實現、更改、測試、維護和重用的物件)。備忘錄模式是由 Noah Thompson、David Espiritu 和 Drew Clinkenbeard 博士為惠普早期產品創建的。
備忘錄設計模式可以解決什麼問題?
編輯- 物件的內部狀態須要保存在外部,以便之後可將物件回復到此狀態。
- 不違反物件的封裝性。
問題在於,一個良好設計的物件會被封裝起來,其表示(資料結構)是隱藏在物件內部,且無法從外部存取。
備忘錄設計模式描述了什麼解決方案?
編輯讓一個物件(發起者)本身負責
- 將其內部狀態保存到(備忘錄)物件,並且
- 從(備忘錄)物件回復到以前的狀態。
只有產生此備忘錄的發起者可以存取它。
客戶端(管理員)可以向發起者請求備忘錄(以保存發起者的內部狀態)並將備忘錄傳遞迴發起者(以恢復到先前的狀態)。
這使得外部能夠在不破壞其封裝的情況下,保存和回復發起者的內部狀態。
請參考下面的 UML 類別圖和序列圖。
結構
編輯UML 類別圖和序列圖
編輯在上面的UML類圖中, Caretaker
類別指向向 Originator
類別,用於保存( createMemento()
)和恢復( restore(memento)
)始發者的內部狀態。Originator
類別實現了(1) createMemento()
,藉由創建並返回 Memento
物件,其記錄了發起者當前內部狀態,(2) restore(memento)
,藉由將 Memento
物件傳入,以回復狀態。
UML序列圖顯示了運行時交動:(1) 保存發起者的內部狀態: Caretaker
對象在Originator
對象上調用createMemento()
,創建一個Memento
對象,保存其當前的內部狀態 ( setState()
),並將Memento
返回給Caretaker
。(2) 恢復Originator的內部狀態: Caretaker
對Originator
對象調用restore(memento)
,並指定存儲應恢復狀態的Memento
對象。 Originator
從Memento
獲取狀態 ( getState()
) 以設置自己的狀態。
Java 範例
編輯以下的 Java 程序說明了備忘錄模式的「撤消變更」用法。
import java.util.List;
import java.util.ArrayList;
class Originator {
private String state;
// 這個類別可以包含其它不用存在 State 中的備忘錄
public void set(String state) {
this.state = state;
System.out.println("Originator: Setting state to " + state);
}
public Memento saveToMemento() {
System.out.println("Originator: Saving to Memento.");
return new Memento(this.state);
}
public void restoreFromMemento(Memento memento) {
this.state = memento.getSavedState();
System.out.println("Originator: State after restoring from Memento: " + state);
}
public static class Memento {
private final String state;
public Memento(String stateToSave) {
state = stateToSave;
}
// accessible by outer class only
private String getSavedState() {
return state;
}
}
}
class Caretaker {
public static void main(String[] args) {
List<Originator.Memento> savedStates = new ArrayList<Originator.Memento>();
Originator originator = new Originator();
originator.set("State1");
originator.set("State2");
savedStates.add(originator.saveToMemento());
originator.set("State3");
// 我們可以要求多個備忘錄,並選擇要回復到那個狀態。
savedStates.add(originator.saveToMemento());
originator.set("State4");
originator.restoreFromMemento(savedStates.get(1));
}
}
輸出是:
Originator: Setting state to State1 Originator: Setting state to State2 Originator: Saving to Memento. Originator: Setting state to State3 Originator: Saving to Memento. Originator: Setting state to State4 Originator: State after restoring from Memento: State3
本示例使用 String 作為狀態,它是 Java 中的不可變物件。在現實生活中,狀態幾乎總是一個可變物件,在這種情況下,必須創建狀態的副本。
必須指出的是,所展示的實現有一個缺點:它聲明了一個內部類。如果備忘錄可以適用於多個發起者,則會更好。
實現Memento 還有其他三種主要方式:
- 序列化。
- 在同一個包中聲明的類別。
- 還可以通過代理來訪問該物件,代理可以實現對物件的任何保存/恢復操作。
C# 示例
編輯備忘錄模式允許人們在不違反封裝的情況下捕獲物件的內部狀態,以便以後可以根據需求來撤消變更/回復變更。從這裡可以看出,備忘錄物件實際上用於恢復物件中所做的更改。
class Memento
{
private readonly string savedState;
private Memento(string stateToSave)
{
savedState = stateToSave;
}
public class Originator
{
private string state;
// The class could also contain additional data that is not part of the
// state saved in the memento.
public void Set(string state)
{
Console.WriteLine("Originator: Setting state to " + state);
this.state = state;
}
public Memento SaveToMemento()
{
Console.WriteLine("Originator: Saving to Memento.");
return new Memento(state);
}
public void RestoreFromMemento(Memento memento)
{
state = memento.savedState;
Console.WriteLine("Originator: State after restoring from Memento: " + state);
}
}
}
class Caretaker
{
static void Main(string[] args)
{
var savedStates = new List<Memento>();
var originator = new Memento.Originator();
originator.Set("State1");
originator.Set("State2");
savedStates.Add(originator.SaveToMemento());
originator.Set("State3");
// We can request multiple mementos, and choose which one to roll back to.
savedStates.Add(originator.SaveToMemento());
originator.Set("State4");
originator.RestoreFromMemento(savedStates[1]);
}
}
Python 範例
編輯"""
備忘錄模式範例
"""
class Originator:
_state = ""
def set(self, state: str) -> None:
print(f"Originator: Setting state to {state}")
self._state = state
def save_to_memento(self) -> "Memento":
return self.Memento(self._state)
def restore_from_memento(self, m: "Memento") -> None:
self._state = m.get_saved_state()
print(f"Originator: State after restoring from Memento: {self._state}")
class Memento:
def __init__(self, state):
self._state = state
def get_saved_state(self):
return self._state
saved_states = []
originator = Originator()
originator.set("State1")
originator.set("State2")
saved_states.append(originator.save_to_memento())
originator.set("State3")
saved_states.append(originator.save_to_memento())
originator.set("State4")
originator.restore_from_memento(saved_states[1])
JavaScript 範列
編輯// The Memento pattern is used to save and restore the state of an object.
// A memento is a snapshot of an object's state.
var Memento = {// Namespace: Memento
savedState : null, // The saved state of the object.
save : function(state) { // Save the state of an object.
this.savedState = state;
},
restore : function() { // Restore the state of an object.
return this.savedState;
}
};
// The Originator is the object that creates the memento.
// defines a method for saving the state inside a memento.
var Originator = {// Namespace: Originator
state : null, // The state to be stored
// Creates a new originator with an initial state of null
createMemento : function() {
return {
state : this.state // The state is copied to the memento.
};
},
setMemento : function(memento) { // Sets the state of the originator from a memento
this.state = memento.state;
}
};
// The Caretaker stores mementos of the objects and
// provides operations to retrieve them.
var Caretaker = {// Namespace: Caretaker
mementos : [], // The mementos of the objects.
addMemento : function(memento) { // Add a memento to the collection.
this.mementos.push(memento);
},
getMemento : function(index) { // Get a memento from the collection.
return this.mementos[index];
}
};
var action_step = "Foo"; // The action to be executed/the object state to be stored.
var action_step_2 = "Bar"; // The action to be executed/the object state to be stored.
// 設置初始值
Originator.state = action_step;
Caretaker.addMemento(Originator.createMemento());// save the state to the history
console.log("Initial State: " + Originator.state); // Foo
// 改變初始值
Originator.state = action_step_2;
Caretaker.addMemento(Originator.createMemento()); // save the state to the history
console.log("State After Change: " + Originator.state); // Bar
// 回復到第一個狀態——撤消變更
Originator.setMemento(Caretaker.getMemento(0));
console.log("State After Undo: " + Originator.state); // Foo
// 回復到第二個狀態——重做變更
Originator.setMemento(Caretaker.getMemento(1));
console.log("State After Redo: " + Originator.state); // Bar
參考
編輯- ^ The Memento design pattern - Structure and Collaboration. w3sDesign.com. [2017-08-12].