18.備忘錄模式(Memento Pattern)
阿新 • • 發佈:2019-01-25
引子
俗話說:世上難買後悔藥。所以凡事講究個“三思而後行”,但總常見有人做“痛心疾首”狀:當初我要是……。如果真的有《大話西遊》中能時光倒流的“月光寶盒”,那這世上也許會少一些傷感與後悔——當然這隻能是痴人說夢了。
但是在我們手指下的程式世界裡,卻有的後悔藥買。今天我們要講的備忘錄模式便是程式世界裡的“月光寶盒”。
所謂備忘錄模式就是在執行某個命令之前先將當前狀態備份,執行完,在某種情況下需要將狀態回滾。
1.定義
在不破壞封裝性的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態。這樣以後就可以將該物件恢復到原先儲存的狀態。
2.備忘錄模式的三個角色
- Originator發起人角色:記錄當前時刻的內部狀態,負責定義哪些屬於備份範圍的狀態,負責建立和恢復備忘錄資料。
- Memento備忘錄角色:負責儲存Originator發起人物件的內部狀態,在需要的時候提供發起人需要內部狀態。
- Caretaker備忘錄管理員角色:對備忘錄進行管理、儲存和提供備忘錄。
3.備忘錄模式的使用場景
需要儲存和恢復資料的相關場景
提供一個可回滾(rollback)的操作;比如CTRL+Z組合鍵,IE瀏覽器的後退按鈕等
需要監控的副本場景中:例如需要監控一個物件的屬性,但是監控又不應該作為系統的主要業務來呼叫,它只是邊緣應用,即使出現監控不準,錯誤報警也影響不大,因此一般的做法是備份一個主執行緒中的物件,然後由分析程式來分析
資料庫連線的事物管理就是一個備忘錄模式,想想看,如果你要實現一個JDBC驅動,如何來實現事物?還不是使用備忘錄模式!
下面是備忘錄模式的通用程式碼:
package _18MementoPattern; /** * 備忘錄角色 */ public class Memento { // 發起人的內部狀態的備份 private String state = ""; public Memento(String state) { this.state = state; } public String getState() { return state; } public void setState(String state) { this.state = state; } }
package _18MementoPattern;
/**
* 發起人角色
*/
public class Originator {
// 發起人的內部狀態
private String state = "";
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
// 建立一個備忘錄
public Memento createMemento()
{
return new Memento(state);
}
// 使用備忘錄恢復狀態
public void recover(Memento memento)
{
this.state = memento.getState();
}
}
package _18MementoPattern;
/**
* 備忘錄管理員
*/
public class Caretaker {
// 管理員負責管理備忘錄
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
package _18MementoPattern;
/**
* 場景類
*/
public class Client {
public static void main(String[] args) {
// 初始狀態
Originator originator = new Originator();
originator.setState("開心");
System.out.println("初始狀態:" + originator.getState());
// 備份初始狀態
Caretaker caretaker = new Caretaker();
caretaker.setMemento(originator.createMemento());
// 表白被拒絕了
originator.setState("悲傷");
System.out.println("表白被拒:" + originator.getState());
// 恢復狀態
originator.recover(caretaker.getMemento());
System.out.println("恢復狀態:" + originator.getState());
}
}
4.備忘錄模式的注意事項
- 備忘錄的生命週期:備忘錄創建出來就要在“最近”的程式碼中使用,要主動管理它的生命週期,不使用時刪除其引用,等待垃圾回收。
- 備忘錄的效能:不要在頻繁建立備份的場景中使用備忘錄模式(比如for迴圈),原因有二:一是控制不了備忘錄建立的物件數量,二是大物件的建立是要消耗資源的,系統的效能需要考慮。
5.備忘錄模式的擴充套件
5.1 clone方式的備忘錄
當然備忘錄模式也可以靈活變化,比如發起人融合了備忘錄角色:
package _18MementoPattern;
/**
* 發起人角色融合了備忘錄角色
*/
public class Originator2 implements Cloneable {
// 發起人的內部狀態
private String state = "";
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
// 建立一個備忘錄
public Originator2 createMemento()
{
return this.clone();
}
// 使用備忘錄恢復狀態
public void recover(Originator2 originator2)
{
this.state = originator2.getState();
}
@Override
protected Originator2 clone(){
try {
return (Originator2)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
還可以更簡單,發起人不僅融合了備忘錄角色,還融合了備忘錄管理員角色:
package _18MementoPattern;
/**
* 發起人角色同事融合了備忘錄角色和管理員角色
*/
public class Originator3 implements Cloneable {
// 備忘錄
private Originator3 backup;
// 發起人的內部狀態
private String state = "";
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
// 建立一個備忘錄
public void createMemento()
{
backup = this.clone();
}
// 使用備忘錄恢復狀態
public void recover()
{
this.state = backup.getState();
}
@Override
protected Originator3 clone(){
try {
return (Originator3)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
5.2 多狀態的備忘錄模式
5.3 多備份的備忘錄模式