一、定義
在不破壞封裝性的前提下, 捕獲一個對象的內部狀態, 並在該對象之外保存這個狀態。 這樣以後就可將該對象恢復到原先保存的狀態。
二、示例
相信我們在工作中都用過word編輯器吧,word中有一項功能,當我們的編輯出錯時,Ctrl+Z可以使我們回退到以前的文本,這樣我們就可以在以前文本的基礎上繼續編輯,而不需要從頭開始編輯。現在我們應用備忘錄模式實現一下這個功能。
類圖如下:
首先我們要創建一個文檔編輯器,負責編輯文本,備份文本,及恢復備份的文本
package memento;
/**
* 文檔編輯器
*/
public class Word {
private String text;//文本
//獲取文本內容
public String getText() {
return text;
}
//編輯文本內容
public void setText(String text) {
this.text = text;
}
//創建一個備忘錄,並且保存文本到備忘錄中
public Memento createMemento(String text){
return new Memento(text);
}
//從備忘錄中恢復文本
public void restoreText(Memento memento){
this.setText(memento.getText());
}
}
其次我們要創建一個備忘錄類,保存我們備份的文本
package memento;
/**
* 備忘錄類
*/
public class Memento {
private String text;//備份的文本
//構造方法,傳入要備份的文本
public Memento(String text) {
super();
this.text = text;
}
//獲取備份文本
public String getText() {
return text;
}
//備份文本到備忘錄中
public void setText(String text) {
this.text = text;
}
}
我們還要創建一個備忘錄管理類,來管理我們的備忘錄
package memento;
import java.util.HashMap;
/**
* 備忘錄管理類
*/
public class MementoManager {
private HashMap<String,Memento> mementos=new HashMap<String,Memento>();//保存備忘錄,根據時間來區分備忘錄
//添加備忘錄
public void addMemento(String time,Memento memento){
if(this.mementos==null){
this.mementos=new HashMap<String,Memento>();
}
this.mementos.put(time,memento);
}
//根據時間獲取備忘錄
public Memento getMemento(String time){
return this.mementos.get(time);
}
}
測試類
package memento;
/**
* 場景類
*/
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Word word=new Word();//新建一個文檔
MementoManager manager=new MementoManager();//備份管理器
word.setText("第一行文本");//寫入第一行文本
System.out.println("寫入文本,當前文本:\n"+word.getText());
manager.addMemento("15:38",word.createMemento(word.getText()));//創建一個備份並添加到管理器中
System.out.println("======備份======");
word.setText("第一行文本\n第二行文本");//寫入第二行文本
System.out.println("寫入文本,當前文本:\n"+word.getText());
word.restoreText(manager.getMemento("15:38"));
System.out.println("恢復,當前文本:\n"+word.getText());
}
}
運行結果:
三、角色
通俗地說, 備忘錄模式就是一個對象的備份模式, 提供了一種程序數據的備份方法。一般來說,有三種角色:
- Originator 發起人角色
記錄當前時刻的內部狀態, 負責定義哪些屬於備份範圍的狀態, 負責創建和恢復備忘錄數據。例如上例中的:Word.class。 - Cementor 備忘錄角色
負責存儲Originator發起人對象的內部狀態, 在需要的時候提供發起人需要的內部狀態。例如上例中的:Cementor.class。 - CementorManager 備忘錄管理員角色
對備忘錄進行管理、 保存和提供備忘錄。例如上例中的:CementorManager.class。
四、使用場景
- 需要保存和恢復數據的相關狀態場景。
- 提供一個可回滾( rollback) 的操作; 比如Word中的CTRL+Z組合鍵, IE瀏覽器中的後退按鈕, 文件管理器上的backspace鍵等。
- 需要監控的副本場景中。 例如要監控一個對象的屬性, 但是監控又不應該作爲系統的主業務來調用, 它只是邊緣應用, 即使出現監控不準、 錯誤報警也影響不大, 因此一般的做法是備份一個主線程中的對象, 然後由分析程序來分析。
五、注意事項
- 備忘錄的生命期
備忘錄創建出來就要在“最近”的代碼中使用, 要主動管理它的生命週期, 建立就要使用, 不使用就要立刻刪除其引用, 等待垃圾回收器對它的回收處理。 - 備忘錄的性能
不要在頻繁建立備份的場景中使用備忘錄模式( 比如一個for循環中) , 原因有二: 一是控制不了備忘錄建立的對象數量; 二是大對象的建立是要消耗資源的, 系統的性能需要考慮。
六、clone方式的備忘錄
在原型模式的時候,我們可以通過複製的方法產生一個對象的內部狀態,這個一個很好的方法,我們只需要發起者角色實現Cloneable接口:
package memento;
/**
* 實現Cloneable接口,既是發起者角色,也是備忘錄角色
*/
public class CloneableWord implements Cloneable{
private String text;//文本
private CloneableWord backup;//備份對象
//獲取文本內容
public String getText() {
return text;
}
//設置文本內容
public void setText(String text) {
this.text = text;
}
//創建備份
public void createMemento(){
this.backup=(CloneableWord) this.clone();//通過clone()方法複製對象的狀態給備份對象
}
//從備份中恢復文本
public void restoreText(){
this.setText(this.backup.getText());//從備份對象恢復對象狀態
}
@Override
protected Object clone() {
try{
return (CloneableWord)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return null;
}
}
增加一個備份對象,通過clone()方法完整複製一個對象的狀態到備份對象,需要的時候再通過備份對象還原。這樣,CloneableWord既是發起人角色,也是備忘錄角色。
package memento;
/**
* 場景類
*/
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
CloneableWord word=new CloneableWord();//新建一個文檔
word.setText("第一行文本");//寫入第一行文本
System.out.println("寫入文本,當前文本:\n"+word.getText());
word.createMemento();//備份
System.out.println("======備份======");
word.setText("第一行文本\n第二行文本");//寫入第二行文本
System.out.println("寫入文本,當前文本:\n"+word.getText());
word.restoreText();//恢復文本
System.out.println("恢復,當前文本:\n"+word.getText());
}
}
運行結果:
與前面實現方式完全一致。