設計模式系列—備忘錄模式
模式定義
在不破壞封裝性的前提下,捕獲一個對象的內部狀態,并在該對象之外保存這個狀態,以便以后當需要時能將該對象恢復到原先保存的狀態。該模式又叫快照模式。
模版實現如下:
- package com.niuh.designpattern.memento.v1;
- /**
- * <p>
- * 備忘錄模式
- * </p>
- */
- public class MementoPattern {
- public static void main(String[] args) {
- Originator or = new Originator();
- Caretaker cr = new Caretaker();
- or.setState("S0");
- System.out.println("初始狀態:" + or.getState());
- cr.setMemento(or.createMemento()); //保存狀態
- or.setState("S1");
- System.out.println("新的狀態:" + or.getState());
- or.restoreMemento(cr.getMemento()); //恢復狀態
- System.out.println("恢復狀態:" + or.getState());
- }
- }
- //備忘錄
- class Memento {
- private String state;
- public Memento(String state) {
- this.state = state;
- }
- public void setState(String state) {
- this.state = state;
- }
- public String getState() {
- return state;
- }
- }
- //發起人
- class Originator {
- private String state;
- public void setState(String state) {
- this.state = state;
- }
- public String getState() {
- return state;
- }
- public Memento createMemento() {
- return new Memento(state);
- }
- public void restoreMemento(Memento m) {
- this.setState(m.getState());
- }
- }
- //管理者
- class Caretaker {
- private Memento memento;
- public void setMemento(Memento m) {
- memento = m;
- }
- public Memento getMemento() {
- return memento;
- }
- }
輸出結果如下:
- 初始狀態:S0
- 新的狀態:S1
- 恢復狀態:S0
解決的問題
備忘錄模式能記錄一個對象的內部狀態,當用戶后悔時能撤銷當前操作,使數據恢復到它原先的狀態。
每個人都有犯錯誤的時候,都希望有種“后悔藥”能彌補自己的過失,讓自己重新開始,但現實是殘酷的。在計算機應用中,客戶同樣會常常犯錯誤,能否提供“后悔藥”給他們呢?當然是可以的,而且是有必要的。這個功能由“備忘錄模式”來實現。
模式組成
備忘錄模式的核心是設計備忘錄類以及用于管理備忘錄的管理者類。
實例說明
實例概況
以游戲存檔為例,看一下如何用備忘錄模式實現
使用步驟
步驟1:定義備忘錄角色,用于存儲角色狀態。
- class RoleStateMemento {
- private int vit; //生命力
- private int atk; //攻擊力
- private int def; //防御力
- public RoleStateMemento(int vit, int atk, int def) {
- this.vit = vit;
- this.atk = atk;
- this.def = def;
- }
- public int getVit() {
- return vit;
- }
- public void setVit(int vit) {
- this.vit = vit;
- }
- public int getAtk() {
- return atk;
- }
- public void setAtk(int atk) {
- this.atk = atk;
- }
- public int getDef() {
- return def;
- }
- public void setDef(int def) {
- this.def = def;
- }
- }
步驟2:定義發起人角色(當前游戲角色),記錄當前游戲角色的生命力、攻擊力、防御力。通過saveState()方法來保存當前狀態,通過recoveryState()方法來恢復角色狀態。
- class GameRole {
- private int vit; //生命力
- private int atk; //攻擊力
- private int def; //防御力
- public int getVit() {
- return vit;
- }
- public void setVit(int vit) {
- this.vit = vit;
- }
- public int getAtk() {
- return atk;
- }
- public void setAtk(int atk) {
- this.atk = atk;
- }
- public int getDef() {
- return def;
- }
- public void setDef(int def) {
- this.def = def;
- }
- //狀態顯示
- public void stateDisplay() {
- System.out.println("角色當前狀態:");
- System.out.println("體力:" + this.vit);
- System.out.println("攻擊力:" + this.atk);
- System.out.println("防御力: " + this.def);
- System.out.println("-----------------");
- }
- //獲得初始狀態
- public void getInitState() {
- this.vit = 100;
- this.atk = 100;
- this.def = 100;
- }
- //戰斗后
- public void fight() {
- this.vit = 0;
- this.atk = 0;
- this.def = 0;
- }
- //保存角色狀態
- public RoleStateMemento saveState() {
- return (new RoleStateMemento(vit, atk, def));
- }
- //恢復角色狀態
- public void recoveryState(RoleStateMemento memento) {
- this.vit = memento.getVit();
- this.atk = memento.getAtk();
- this.def = memento.getDef();
- }
- }
步驟3:定義管理者角色,角色狀態管理者
- class RoleStateCaretaker {
- private RoleStateMemento memento;
- public RoleStateMemento getMemento() {
- return memento;
- }
- public void setMemento(RoleStateMemento memento) {
- this.memento = memento;
- }
- }
步驟4:測試輸出
- public class MementoPattern {
- // 邏輯大致為打boss前存檔,打boss失敗了
- public static void main(String[] args) {
- //打boss前
- GameRole gameRole = new GameRole();
- gameRole.getInitState();
- gameRole.stateDisplay();
- //保存進度
- RoleStateCaretaker caretaker = new RoleStateCaretaker();
- caretaker.setMemento(gameRole.saveState());
- //打boss失敗
- gameRole.fight();
- gameRole.stateDisplay();
- //恢復狀態
- gameRole.recoveryState(caretaker.getMemento());
- gameRole.stateDisplay();
- }
- }
輸出結果
- 角色當前狀態:
- 體力:100
- 攻擊力:100
- 防御力: 100
- -----------------
- 角色當前狀態:
- 體力:0
- 攻擊力:0
- 防御力: 0
- -----------------
- 角色當前狀態:
- 體力:100
- 攻擊力:100
- 防御力: 100
優點
備忘錄模式是一種對象行為型模式,其主要優點如下。
- 提供了一種可以恢復狀態的機制。當用戶需要時能夠比較方便地將數據恢復到某個歷史的狀態。
- 實現了內部狀態的封裝。除了創建它的發起人之外,其他對象都不能夠訪問這些狀態信息。
- 簡化了發起人類。發起人不需要管理和保存其內部狀態的各個備份,所有狀態信息都保存在備忘錄中,并由管理者進行管理,這符合單一職責原則。
缺點
資源消耗大。如果要保存的內部狀態信息過多或者特別頻繁,將會占用比較大的內存資源。
注意事項
- 為了符合迪米特法則,需要有一個管理備忘錄的類
- 不要在頻繁建立備份的場景中使用備忘錄模式。為了節約內存,可使用原型模式+備忘錄模式
應用場景
- 需要保存和恢復數據的相關場景
- 提供一個可回滾的操作,如ctrl+z、瀏覽器回退按鈕、Backspace鍵等
- 需要監控的副本場景
模式的擴展
在備忘錄模式中,有單狀態備份的例子,也有多狀態備份的例子。可以結合原型模式混合使用。在備忘錄模式中,通過定義“備忘錄”來備份“發起人”的信息,而原型模式的 clone() 方法具有自備份功能,所以,如果讓發起人實現 Cloneable 接口就有備份自己的功能,這時可以刪除備忘錄類,其結構如下:
源碼中的應用
- #Spring
- org.springframework.binding.message.StateManageableMessageContext
StateManageableMessageContext 部分源碼
- public interface StateManageableMessageContext extends MessageContext {
- /**
- * Create a serializable memento, or token representing a snapshot of the internal state of this message context.
- * @return the messages memento
- */
- public Serializable createMessagesMemento();
- /**
- * Set the state of this context from the memento provided. After this call, the messages in this context will match
- * what is encapsulated inside the memento. Any previous state will be overridden.
- * @param messagesMemento the messages memento
- */
- public void restoreMessages(Serializable messagesMemento);
- /**
- * Configure the message source used to resolve messages added to this context. May be set at any time to change how
- * coded messages are resolved.
- * @param messageSource the message source
- * @see MessageContext#addMessage(MessageResolver)
- */
- public void setMessageSource(MessageSource messageSource);
- }
PS:以上代碼提交在 Github :
https://github.com/Niuh-Study/niuh-designpatterns.git