備忘錄模式提供了一種物件狀態的撤銷實現機制,當系統中某一物件需要恢復到某一歷史狀態時可以使用備忘錄模式來進行設計
模式動機
人人都有後悔的時候,在軟體使用過程中難免會出現一些誤操作,如不小心刪除了某些文字或圖片,資料填入錯誤等,對於這些誤操作,需要提供一種後悔藥機制,讓系統可以回到誤操作前的狀態,這就是備忘錄模式的模式動機
模式定義
在不破壞封裝的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態,這樣可以在以後將物件恢復到原先儲存的狀態。備忘錄模式是一種物件行為型模式,其別名為 Token
模式結構
-
Originator(原發器)
原發器可以建立一個備忘錄,並儲存它的當前內部狀態,也可以使用備忘錄來恢復其內部狀態。一般將需要儲存內部狀態的類設計為原發器。
-
Memento(備忘錄)
儲存原發器的內部狀態,根據原發器來決定儲存哪些內部狀態。需要注意的是,除了原發器本身與負責人之外,備忘錄物件不能直接供其他類使用。
-
Caretaker(負責人)
負責人又稱管理者,它負責儲存備忘錄,但是不能對備忘錄的內容進行操作或檢查。
模式分析
前面已經說了,備忘錄模式就是用來吃後悔藥的,理解起來並不難,關鍵在於如何設計備忘錄類和負責人類。
備忘錄中儲存的是原發器的中間狀態,因此需要防止原發器以外的其他物件訪問備忘錄。也不能在備忘錄物件之外儲存原發器狀態,如果暴露其內部狀態將違反封裝的原則。
為了實現對備忘錄物件的封裝,需要對備忘錄的呼叫進行控制。對於原發器而言,它可以呼叫備忘錄的所有資訊,允許原發器訪問先前狀態的所有資料。對於負責人而言,只負責備忘錄的儲存並將備忘錄傳遞給其他物件。對於其他物件而言,只需要從負責人處取出備忘錄物件並將原發器物件的狀態恢復,而無須關心備忘錄的儲存狀態。
下面通過一個例項來進一步理解備忘錄模式。
模式例項
某系統提供了使用者資訊操作模組,使用者可以修改自己的各項資訊,使用者在進行了錯誤操作後可以恢復到操作之前的狀態。
-
原發器 UserInfoDTO(使用者資訊類)
package dp.memento; public class UserInfoDTO { private String account; private String password; public String getAccount() { return account; } public void setAccount(String account) { this.account = account; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } // 建立一個備忘錄物件 public Memento saveMemento() { return new Memento(account, password); } // 根據備忘錄物件恢復原發器狀態 public void restoreMemento(Memento memento) { this.account = memento.getAccount(); this.password = memento.getPassword(); } public void show() { System.out.println("Account: " + this.account); System.out.println("Password: " + this.getPassword()); } }
-
備忘錄 Memento
設計備忘錄時需要考慮到封裝性,即除了原發器類,不允許其他類來呼叫其建構函式與相關方法。一般將備忘錄類和原發器類定義在同一包中來實現封裝,使用預設訪問識別符號來定義備忘錄類,即保證包內可見性。
package dp.memento; class Memento { private String account; private String password; public Memento(String account, String password) { this.account = account; this.password = password; } public String getAccount() { return account; } public void setAccount(String account) { this.account = account; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
-
負責人 Caretaker
package dp.memento; public class Caretaker { private Memento memento; public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } }
-
客戶端測試類 Client
import dp.memento.UserInfoDTO; import dp.memento.Caretaker; public class Client { public static void main(String[] args) { UserInfoDTO user = new UserInfoDTO(); // 建立負責人 Caretaker caretaker = new Caretaker(); user.setAccount("zhangsan"); user.setPassword("123456"); System.out.println("狀態一"); user.show(); // 儲存備忘錄 caretaker.setMemento(user.saveMemento()); System.out.println("------------------------"); user.setPassword("11111111"); System.out.println("狀態二"); user.show(); System.out.println("------------------------"); // 從備忘錄中恢復 user.restoreMemento(caretaker.getMemento()); System.out.println("回到狀態一"); user.show(); System.out.println("------------------------"); } }
-
執行結果
模式優缺點
備忘錄模式優點:
- 提供了一種狀態恢復的實現機制,使得使用者可以方便地回到特定的一個歷史步驟
- 實現了資訊的封裝,備忘錄只儲存原發器的狀態,不會被其他程式碼修改。採用堆疊來儲存備忘錄物件可以實現多次撤銷操作,可以通過在負責人中定義集合物件來儲存多個備忘錄。
備忘錄模式缺點:
- 每儲存一次物件的狀態都需要消耗記憶體資源,資源消耗過大
模式適用環境
在以下情況可以使用備忘錄模式:
- 儲存一個物件在某一時刻的狀態或部分狀態
- 如果用一個介面來讓其他物件得到這些狀態,將會暴露物件的實現細節並破壞封裝性,一個物件不希望外界直接訪問得到其內部狀態,通過負責人可以間接訪問其內部狀態