物件導向程式強調封裝性,在可以不曝露內部狀態的情況下,就儘可能不曝露內部狀態,以提高程式的安全性,然
而有時候,我們想要保留物件的內部狀態,但即然物件已經封裝了內部資訊,我又要如何儲存其內部狀態,又不破壞其封裝性呢?這似乎是兩相矛盾的事。
有些物件可能在建立時,依您給它的建構參數來設定初始狀態,除此之外,您並沒有任何的方式來設定其內部成員,例如setName()之類的方法,然而隨著程式的運行,該物件可能會自行改變內部狀態,或是由於其它的操作或導致狀態改變,您希望您的程式中對該物件有復原機制,能夠隨時儲存某個時間點的狀態,並在適當的時候可以取出還原,這個時候您可以使用備忘錄(Memento)模式來達成這個功能。
備忘錄模式由原發者(Originator)在物件內創造一個備忘錄(Memento)物件,由於是在物件內創造的,我們可以將原發者的內部物件記錄在備忘錄物件中,如果以後要復原物件狀態,只要取回備忘錄物件,就可以進行復原,使用者不會直接接觸到物件的內部狀態,封裝性也就可以保留。
備忘錄模式的UML類別結構圖如下所示:
圖中的Caretaker是用來保留原發者所創造的備忘錄物件,以供日後復原時取回,state表示一個內部狀態,內部狀態多時,也可以將之組織為一個類別。
下面提供一個簡單的實作,看看如何實現備忘錄模式:
public class Originator {
private String _name;
private String _phone;
public Originator(String name, String phone) {
_name = name;
_phone = phone;
}
public void someOperation() {
_name = "noboby";
_phone = "911-911";
}
public void setMemento(Memento m) {
_name = m.getName();
_phone = m.getPhone();
}
public Memento createMemento() {
return new Memento(_name, _phone);
}
public void showInfo() {
System.out.println("Name: " + _name +
"\nPhone: " + _phone + "\n");
}
}
public class Memento {
private String _name;
private String _phone;
public Memento(String name, String phone) {
_name = name;
_phone = phone;
}
public String getName() {
return _name;
}
public String getPhone() {
return _phone;
}
public void setName(String name) {
_name = name;
}
public void setPhone(String phone) {
_phone = phone;
}
}
public class Caretaker {
private Memento _memento;
public void setMemento(Memento memento) {
_memento = memento;
}
public Memento getMemento() {
return _memento;
}
}
public class Main {
public static void main(String[] args) {
Originator originator = new Originator("Justin", "888-8888");
Caretaker caretaker = new Caretaker();
caretaker.setMemento(originator.createMemento());
originator.showInfo();
originator.someOperation();
originator.showInfo();
originator.setMemento(caretaker.getMemento());
originator.showInfo();
}
}
執行的結果如下:
Name: Justin
Phone: 888-8888
Name: noboby
Phone: 911-911
Name: Justin
Phone: 888-8888
我們可以使用命令(Command)模式來實作Redo/Undo的功能,然而有一個前題是您必須儲存被操作物件的狀態,這時候我們就可以使用備忘錄模式,將操作前的物件狀態記錄下來,並記錄所使用的命令,當要實現Undo時,只要取回備忘錄物件以復原物件狀態即可,至於Redo,則是取回命令物件並操作,不過由於額外使用一些空間來記錄這些物件,所以會消耗大量的記憶體是其缺點。