備忘錄模式

開發仿真系統

   考慮這樣一個仿真應用,功能是:模擬運行鍼對某個具體問題的多個解決方案,記錄運行過程的各類數據,在模擬運行完成事後,好對這多個解決方案進行比較和評價,從而選定最優的解決方案。html

這種仿真系統,在不少領域都有應用,好比:工做流系統,對同一問題制定多個流程,而後經過仿真運行,最後來肯定最優的流程作爲解決方案;在工業設計和製造領域,仿真系統的應用就更普遍了。java

因爲都是解決同一個具體的問題,這多個解決方案並非徹底不同的,假定它們的前半部分運行是徹底同樣的,只是在後半部分採用了不一樣的解決方案,後半部分須要使用前半部分運行所產生的數據。設計模式

因爲要模擬運行多個解決方案,並且最後要根據運行結果來進行評價,這就意味着每一個方案的後半部分的初始數據應該是同樣,也就是說在運行每一個方案後半部分以前,要保證數據都是由前半部分運行所產生的數據,固然,我們這裏並不具體的去深刻到底有哪些解決方案,也不去深刻到底有哪些狀態數據,這裏只是示意一下。數組

那麼,這樣的系統該如何實現呢?尤爲是每一個方案運行須要的初始數據應該同樣,要如何來保證呢?緩存

不用模式的解決方案

 要保證初始數據的一致,實現思路也很簡單:數據結構

    首先模擬運行流程第一個階段,獲得後階段各個方案運行須要的數據,並把數據保存下來,以備後用ide

  • 每次在模擬運行某一個方案以前,用保存的數據去從新設置模擬運行流程的對象,這樣運行後面不一樣的方案時,對於這些方案,初始數據就是同樣的了學習

根據上面的思路,來寫出仿真運行的示意代碼,示例代碼以下:測試

/**
 * 模擬運行流程A,只是一個示意,代指某個具體流程
 */
public class FlowAMock {
    /**
     * 流程名稱,不須要外部存儲的狀態數據
     */
    private String flowName;
    /**
     * 示意,代指某個中間結果,須要外部存儲的狀態數據
     */
    private int tempResult;
    /**
     * 示意,代指某個中間結果,須要外部存儲的狀態數據
     */
    private String tempState;
    /**
     * 構造方法,傳入流程名稱
     * @param flowName 流程名稱
     */
    public FlowAMock(String flowName){
       this.flowName = flowName;
    }
   
    public String getTempState() {
       return tempState;
    }
    public void setTempState(String tempState) {
       this.tempState = tempState;
    }
    public int getTempResult() {
       return tempResult;
    }
    public void setTempResult(int tempResult) {
       this.tempResult = tempResult;
    }
   
    /**
     * 示意,運行流程的第一個階段
     */
    public void runPhaseOne(){
       //在這個階段,可能產生了中間結果,示意一下
       tempResult = 3;
       tempState = "PhaseOne";
    }
    /**
     * 示意,按照方案一來運行流程後半部分
     */
    public void schema1(){
       //示意,須要使用第一個階段產生的數據
       this.tempState += ",Schema1";
       System.out.println(this.tempState
+ " : now run "+tempResult);
       this.tempResult += 11;
    }
    /**
     * 示意,按照方案二來運行流程後半部分
     */
    public void schema2(){
       //示意,須要使用第一個階段產生的數據
       this.tempState += ",Schema2";
       System.out.println(this.tempState
+ " : now run "+tempResult);
       this.tempResult += 22;
    }  
}

看看如何使用這個模擬流程的對象,寫個客戶端來測試一下。示例代碼以下:this

public class Client {
    public static void main(String[] args) {
       // 建立模擬運行流程的對象
       FlowAMock mock = new FlowAMock("TestFlow");
       //運行流 程的第一個階段
       mock.runPhaseOne();
       //獲得第一個階段運行所產生的數據,後面要用
       int tempResult = mock.getTempResult();
       String tempState = mock.getTempState();
      
       //按照方案一來運行流程後半部分
       mock.schema1();
      
       //把第一個階段運行所產生的數據從新設置回去
       mock.setTempResult(tempResult);
       mock.setTempState(tempState);
      
       //按照方案二來運行流程後半部分
       mock.schema2();
    }
}

運行結果以下:

PhaseOne,Schema1 : now run 3
PhaseOne,Schema2 : now run 3

仔細看,上面結果中框住的部分,是同樣的值,這說明運行時,它們的初始數據是同樣的,基本知足了功能要求。

看起來實現很簡單,是吧,想想有沒有什麼問題呢?

上面的實現有一個不太好的地方,那就是數據是一個一個零散着在外部存放的,若是須要外部存放的數據多了,會顯得很雜亂。這個好解決,只須要定義一個數據對象來封裝這些須要外部存放的數據就能夠了,上面那樣作是故意的,好提醒你們這個問題。這個就不去示例了。

還有一個嚴重的問題,那就是:爲了把運行期間的數據放到外部存儲起來,模擬流程的對象被迫把內部數據結構開放出來,這暴露了對象的實現細節,並且也破壞了對象的封裝性。原本這些數據只是模擬流程的對象內部數據,應該是不對外的。

那麼究竟如何實現這樣的功能會比較好呢?

備忘錄模式定義

  在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象以外保存這個狀態。這樣之後就可將該對象回覆到原先保存的狀態。

模式結構和說明

wKiom1lhwtSCcDSPAAC0HX-594w570.png


Memento:

       備忘錄。主要用來存儲原發器對象的內部狀態,可是具體須要存儲哪些數據是由原發器對象來決定的。另外備忘錄應該只能由原發器對象來訪問它內部的數據,原發器外部的對象不該該能訪問到備忘錄對象的內部數據。

Originator:

       原發器。使用備忘錄來保存某個時刻原發器自身的狀態,也可使用備忘錄來恢復內部狀態。

Caretaker:

       備忘錄管理者,或者稱爲備忘錄負責人。主要負責保存備忘錄對象,可是不能對備忘錄對象的內容進行操做或檢查。


備忘錄模式示例代碼

(1)先看看備忘錄對象的窄接口,就是那個Memento接口,這個實現最簡單,是個空的接口,沒有任何方法定義,示例代碼以下:

/**
 * 備忘錄的窄接口,沒有任何方法定義
 */
public interface Memento {
    //
}

(2)看看原發器對象,它裏面會有備忘錄對象的實現,由於真正的備忘錄對象看成原發器對象的一個私有內部類來實現了。示例代碼以下:

/**
 * 原發器對象
 */
public class Originator {
    /**
     * 示意,表示原發器的狀態
     */
    private String state = "";
    /**
     * 建立保存原發器對象的狀態的備忘錄對象
     * @return 建立好的備忘錄對象
     */
    public Memento createMemento() {
       return new MementoImpl(state);
    }
    /**
     * 從新設置原發器對象的狀態,讓其回到備忘錄對象記錄的狀態
     * @param memento 記錄有原發器狀態的備忘錄對象
     */
    public void setMemento(Memento memento) {
       MementoImpl mementoImpl = (MementoImpl)memento;
       this.state = mementoImpl.getState();
    }
    /**
     * 真正的備忘錄對象,實現備忘錄窄接口
     * 實現成私有的內部類,不讓外部訪問
     */
    private static class MementoImpl implements Memento{
       /**
        * 示意,表示須要保存的狀態
        */
       private String state = "";
       public MementoImpl(String state){
           this.state = state;
       }
       public String getState() {
           return state;
       }
    }
}

(3)接下來看看備忘錄管理者對象,示例代碼以下:

/**
 * 負責保存備忘錄的對象
 */
public class Caretaker{
    /**
     * 記錄被保存的備忘錄對象
     */
    private Memento memento = null;
    /**
     * 保存備忘錄對象
     * @param memento 被保存的備忘錄對象
     */
    public void saveMemento(Memento memento){
       this.memento = memento;
    }
    /**
     * 獲取被保存的備忘錄對象
     * @return 被保存的備忘錄對象
     */
    public Memento retriveMemento(){
       return this.memento;
    }
}

使用備忘錄模式重寫示例

       學習了備忘錄模式的基本知識事後,來嘗試一下,使用備忘錄模式把前面的示例重寫一下,好看看如何使用備忘錄模式。

  • 首先,那個模擬流程運行的對象,就至關於備忘錄模式中的原發器;

  • 而它要保存的數據,原來是零散的,如今作一個備忘錄對象來存儲這些數據,而且把這個備忘錄對象實現成爲內部類;

  • 固然爲了保存這個備忘錄對象,仍是須要提供管理者對象的;

  • 爲了和管理者對象交互,管理者須要知道保存對象的類型,那就提供一個備忘錄對象的窄接口來供管理者使用,至關於標識了類型。

wKioL1lhw_ihvb1HAAFVBZD0jZc255.png

(1)先來看看備忘錄對象的窄接口吧,示例代碼以下:

/**
 * 模擬運行流程A的對象的備忘錄接口,是個窄接口
 */
public interface FlowAMockMemento {
    //空的
}

(2)再來看看新的模擬運行流程A的對象,至關於原發器對象了,它的變化比較多,大體有以下變化:

  • 首先這個對象原來暴露出去的內部狀態,不用再暴露出去了,也就是內部狀態不用再對外提供getter/setter方法了

  • 在這個對象裏面提供一個私有的備忘錄對象,裏面封裝想要保存的內部狀態,同時讓這個備忘錄對象實現備忘錄對象的窄接口

  • 在這個對象裏面提供建立備忘錄對象,和根據備忘錄對象恢復內部狀態的方法

具體的示例代碼以下:

/**
 * 模擬運行流程A,只是一個示意,代指某個具體流程
 */
public class FlowAMock {
    /**
     * 流程名稱,不須要外部存儲的狀態數據
     */
    private String flowName;
    /**
     * 示意,代指某個中間結果,須要外部存儲的狀態數據
     */
    private int tempResult;
    /**
     * 示意,代指某個中間結果,須要外部存儲的狀態數據
     */
    private String tempState;
    /**
     * 構造方法,傳入流程名稱
     * @param flowName 流程名稱
     */
    public FlowAMock(String flowName){
       this.flowName = flowName;
    }
    /**
     * 示意,運行流程的第一個階段
     */
    public void runPhaseOne(){
       //在這個階段,可能產生了中間結果,示意一下
       tempResult = 3;
       tempState = "PhaseOne";
    }
    /**
     * 示意,按照方案一來運行流程後半部分
     */
    public void schema1(){
       //示意,須要使用第一個階段產生的數據
       this.tempState += ",Schema1";
       System.out.println(this.tempState
+ " : now run "+tempResult);
       this.tempResult += 11;
    }
    /**
     * 示意,按照方案二來運行流程後半部分
     */
    public void schema2(){
       //示意,須要使用第一個階段產生的數據
       this.tempState += ",Schema2";
       System.out.println(this.tempState
+ " : now run "+tempResult);
       this.tempResult += 22;
    }  
    /**
     * 建立保存原發器對象的狀態的備忘錄對象
     * @return 建立好的備忘錄對象
     */
    public FlowAMockMemento createMemento() {
       return new MementoImpl(this.tempResult,this.tempState);
    }
    /**
     * 從新設置原發器對象的狀態,讓其回到備忘錄對象記錄的狀態
     * @param memento 記錄有原發器狀態的備忘錄對象
     */
    public void setMemento(FlowAMockMemento memento) {
       MementoImpl mementoImpl = (MementoImpl)memento;
       this.tempResult = mementoImpl.getTempResult();
       this.tempState = mementoImpl.getTempState();
    }
    /**
     * 真正的備忘錄對象,實現備忘錄窄接口
     * 實現成私有的內部類,不讓外部訪問
     */
    private static class MementoImpl implements FlowAMockMemento{
       /**
        * 示意,保存某個中間結果
        */
       private int tempResult;
       /**
        * 示意,保存某個中間結果
        */
       private String tempState;
       public MementoImpl(int tempResult,String tempState){
           this.tempResult = tempResult;
           this.tempState = tempState;
       }
       public int getTempResult() {
           return tempResult;
       }
       public String getTempState() {
           return tempState;
       }
    }
}

(3)接下來要來實現提供保存備忘錄對象的管理者了,示例代碼以下:

/**
 * 負責保存模擬運行流程A的對象的備忘錄對象
 */
public class FlowAMementoCareTaker {
    /**
     * 記錄被保存的備忘錄對象
     */
    private FlowAMockMemento memento = null;
    /**
     * 保存備忘錄對象
     * @param memento 被保存的備忘錄對象
     */
    public void saveMemento(FlowAMockMemento memento){
       this.memento = memento;
    }
    /**
     * 獲取被保存的備忘錄對象
     * @return 被保存的備忘錄對象
     */
    public FlowAMockMemento retriveMemento(){
       return this.memento;
    }
}

(4)最後來看看,如何使用上面按照備忘錄模式實現的這些對象呢,寫個新的客戶端來測試一下,示例代碼以下:

public class Client {
    public static void main(String[] args) {
       // 建立模擬運行流程的對象
       FlowAMock mock = new FlowAMock("TestFlow");
       //運行流程的第一個階段
       mock.runPhaseOne();     
       //建立一個管理者
       FlowAMementoCareTaker careTaker =
new FlowAMementoCareTaker();
       //建立此時對象的備忘錄對象,並保存到管理者對象那裏,後面要用
       FlowAMockMemento memento = mock.createMemento();
       careTaker.saveMemento(memento);
      
       //按照方案一來運行流程後半部分
       mock.schema1();
      
        //從管理者獲取備忘錄對象,而後設置回去,
       //讓模擬運行流程的對象本身恢復本身的內部狀態
       mock.setMemento(careTaker.retriveMemento());
      
       //按照方案二來運行流程後半部分
       mock.schema2();
    }
}

 運行結果跟前面的示例是同樣的,結果以下:

PhaseOne,Schema1 : now run 3
PhaseOne,Schema2 : now run 3

好好體會一下上面的示例,因爲備忘錄對象是一個私有的內部類,外面只能經過備忘錄對象的窄接口來獲取備忘錄對象,而這個接口沒有任何方法,僅僅起到了一個標識對象類型的做用,從而保證內部的數據不會被外部獲取或是操做,保證了原發器對象的封裝性,也就再也不暴露原發器對象的內部結構了。

再次實現可撤銷操做

       在命令模式中,講到了可撤銷的操做,在那裏講到:有兩種基本的思路來實現可撤銷的操做,一種是補償式或者反操做式:好比被撤銷的操做是加的功能,那撤消的實現就變成減的功能;同理被撤銷的操做是打開的功能,那麼撤銷的實現就變成關閉的功能。

       另一種方式是存儲恢復式,意思就是把操做前的狀態記錄下來,而後要撤銷操做的時候就直接恢復回去就能夠了。

       這裏就該來實現第二種方式,就是存儲恢復式,爲了讓你們更好的理解可撤銷操做的功能,仍是用原來的那個例子,對比學習會比較清楚。

       這也至關因而命令模式和備忘錄模式結合的一個例子,並且因爲命令列表的存在,對應保存的備忘錄對象也是多個。

1:範例需求

考慮一個計算器的功能,最簡單的那種,只能實現加減法運算,如今要讓這個計算器支持可撤銷的操做。

2:存儲恢復式的解決方案

       存儲恢復式的實現,可使用備忘錄模式,大體實現的思路以下:

  • 把原來的運算類,就是那個Operation類,看成原發器,原來的內部狀態result,就只提供一個getter方法,來讓外部獲取運算的結果

  • 在這個原發器裏面,實現一個私有的備忘錄對象

  • 把原來的計算器類,就是Calculator類,看成管理者,把命令對應的備忘錄對象保存在這裏。當須要撤銷操做的時候,就把相應的備忘錄對象設置回到原發器去,恢復原發器的狀態

一塊兒來看看具體的實現,會更清楚。

(1)定義備忘錄對象的窄接口,示例代碼以下

public interface Memento {    //空的}

(2)定義命令的接口,有幾點修改:

  • 修改原來的undo方法,傳入備忘錄對象

  • 添加一個redo方法,傳入備忘錄對象

  • 添加一個createMemento的方法,獲取須要被保存的備忘錄對象

示例代碼以下:

/**
 * 定義一個命令的接口
 */
public interface Command {
    /**
     * 執行命令
     */
    public void execute();
    /**
     * 撤銷命令,恢復到備忘錄對象記錄的狀態
     * @param m 備忘錄對象
     */
    public void undo(Memento m);
    /**
     * 重作命令,恢復到備忘錄對象記錄的狀態
     * @param m 備忘錄對象
     */
    public void redo(Memento m);
    /**
     * 建立保存原發器對象的狀態的備忘錄對象
     * @return 建立好的備忘錄對象
     */
    public Memento createMemento();
}

(3)再來定義操做運算的接口,至關於計算器類這個原發器對外提供的接口,它須要作以下的調整:

  • 去掉原有的setResult方法,內部狀態,不容許外部操做

  • 添加一個createMemento的方法,獲取須要保存的備忘錄對象

  • 添加一個setMemento的方法,來從新設置原發器對象的狀態

示例代碼以下:

/**
 * 操做運算的接口
 */
public interface OperationApi {
    /**
     * 獲取計算完成後的結果
     * @return 計算完成後的結果
     */
    public int getResult();
    /**
     * 執行加法
     * @param num 須要加的數
     */
    public void add(int num);
    /**
     * 執行減法
     * @param num 須要減的數
     */
    public void substract(int num);
    /**
     * 建立保存原發器對象的狀態的備忘錄對象
     * @return 建立好的備忘錄對象
     */
    public Memento createMemento();
    /**
     * 從新設置原發器對象的狀態,讓其回到備忘錄對象記錄的狀態
     * @param memento 記錄有原發器狀態的備忘錄對象
     */
    public void setMemento(Memento memento);
}

(4)因爲如今撤銷和恢復操做是經過使用備忘錄對象,直接來恢復原發器的狀態,所以就再也不須要按照操做類型來區分了,對於全部的命令實現,它們的撤銷和重作都是同樣的。原來的實現是要區分的,若是是撤銷加的操做,那就是減,而撤銷減的操做,那就是加。如今就不區分了,統一使用備忘錄對象來恢復。

       所以,實現一個全部命令的公共對象,在裏面把公共功能都實現了,這樣每一個命令在實現的時候就簡單了。順便把設置持有者的公共實現也放到這個公共對象裏面來,這樣各個命令對象就不用再實現這個方法了,示例代碼以下:

/**
 * 命令對象的公共對象,實現各個命令對象的公共方法
 */
public abstract class AbstractCommand implements Command{
    /**
     * 具體的功能實現,這裏無論
     */
    public abstract void execute();
    /**
     * 持有真正的命令實現者對象
     */
    protected OperationApi operation = null;
    public void setOperation(OperationApi operation) {
       this.operation = operation;
    }
    public Memento createMemento() {
       return this.operation.createMemento();
    }
    public void redo(Memento m) {
       this.operation.setMemento(m);
    }
    public void undo(Memento m) {
       this.operation.setMemento(m);
    }
}

(5)有了公共的命令實現對象,各個具體命令的實現就簡單了,實現加法命令的對象實現,再也不直接實現Command接口了,而是繼承命令的公共對象,這樣只須要實現跟本身命令相關的業務方法就行了,示例代碼以下:

public class AddCommand extends AbstractCommand{
    private int opeNum;
    public AddCommand(int opeNum){
       this.opeNum = opeNum;
    }
    public void execute() {
       this.operation.add(opeNum);
    }
}

看看減法命令的實現,跟加法命令的實現差很少,示例代碼以下:

public class SubstractCommand extends AbstractCommand{
    
    private int opeNum;
    public SubstractCommand(int opeNum){
        this.opeNum = opeNum;
    }
    public void execute() {
        this.operation.substract(opeNum);
    }
    
}


(6)接下來看看運算類的實現,至關因而原發器對象,它的實現有以下改變:

  • 再也不提供setResult方法,內部狀態,不容許外部來操做

  • 添加了createMemento和setMemento方法的實現

  • 添加實現了一個私有的備忘錄對象

示例代碼以下:

/**
 * 運算類,真正實現加減法運算
 */
public class Operation implements OperationApi{
    /**
     * 記錄運算的結果
     */
    private int result;
    public int getResult() {
        return result;
    }

    public void add(int num){
        result += num;
    }
    public void substract(int num){
        result -= num;
    }
    /**
     * 結果保存到備忘錄實例中
     */
    public Memento createMemento() {
        MementoImpl m = new MementoImpl(result);
        return m;
    }
    /**
     * 使用備忘錄中結果重寫賦值
     */
    public void setMemento(Memento memento) {
        MementoImpl m = (MementoImpl)memento;
        this.result = m.getResult();
    }
    /**
     * 備忘錄對象
     */
    private static class MementoImpl implements Memento{
        private int result = 0;
        public MementoImpl(int result){
            this.result = result;
        }

        public int getResult() {
            return result;
        }
    }
}

(7)接下來該看看如何具體的使用備忘錄對象來實現撤銷操做和重作操做了。一樣在計算器類裏面實現,這個時候,計算器類就至關因而備忘錄模式管理者對象。

       實現思路:因爲對於每一個命令對象,撤銷和重作的狀態是不同的,撤銷是回到命令操做前的狀態,而重作是回到命令操做後的狀態,所以對每個命令,使用一個備忘錄對象的數組來記錄對應的狀態。

       這些備忘錄對象是跟命令對象相對應的,所以也跟命令歷史記錄同樣,設立相應的歷史記錄,它的順序跟命令徹底對應起來。在操做命令的歷史記錄的同時,對應操做相應的備忘錄對象記錄。

示例代碼以下:

/**
 * 計算器類,計算器上有加法按鈕、減法按鈕,還有撤銷和恢復的按鈕
 */
public class Calculator {
    /**
     * 命令的操做的歷史記錄,在撤銷時候用
     */
    private List<Command> undoCmds = new ArrayList<Command>();
    /**
     * 命令被撤銷的歷史記錄,在恢復時候用
     */
    private List<Command> redoCmds = new ArrayList<Command>();
    /**
     * 命令操做對應的備忘錄對象的歷史記錄,在撤銷時候用,
     * 數組有兩個元素,第一個是命令執行前的狀態,第二個是命令執行後的狀態
     */
    private List<Memento[]> undoMementos =
new ArrayList<Memento[]>();
    /**
     * 被撤銷命令對應的備忘錄對象的歷史記錄,在恢復時候用,
     * 數組有兩個元素,第一個是命令執行前的狀態,第二個是命令執行後的狀態
     */
    private List<Memento[]> redoMementos =
new ArrayList<Memento[]>();
   
    private Command addCmd = null;
    private Command substractCmd = null;
    public void setAddCmd(Command addCmd) {
       this.addCmd = addCmd;
    }
    public void setSubstractCmd(Command substractCmd) {
       this.substractCmd = substractCmd;
    }  
 
    public void addPressed(){
       //獲取對應的備忘錄對象,並保存在相應的歷史記錄裏面
       Memento m1 = this.addCmd.createMemento();
      
       //執行命令
       this.addCmd.execute();
//把操做記錄到歷史記錄裏面
       undoCmds.add(this.addCmd);
 
       //獲取執行命令後的備忘錄對象
       Memento m2 = this.addCmd.createMemento();
       //設置到撤銷的歷史記錄裏面
       this.undoMementos.add(new Memento[]{m1,m2});
    }
    public void substractPressed(){
       //獲取對應的備忘錄對象,並保存在相應的歷史記錄裏面    
       Memento m1 = this.substractCmd.createMemento();
      
       //執行命令
       this.substractCmd.execute();
//把操做記錄到歷史記錄裏面
       undoCmds.add(this.substractCmd);
      
       //獲取執行命令後的備忘錄對象
       Memento m2 = this.substractCmd.createMemento();
       //設置到撤銷的歷史記錄裏面
       this.undoMementos.add(new Memento[]{m1,m2});
    }
    public void undoPressed(){
       if(undoCmds.size()>0){
           //取出最後一個命令來撤銷
           Command cmd = undoCmds.get(undoCmds.size()-1);
           //獲取對應的備忘錄對象
           Memento[] ms = undoMementos.get(undoCmds.size()-1);
          
           //撤銷
           cmd.undo(ms[0]);
          
           //若是還有恢復的功能,那就把這個命令記錄到恢復的歷史記錄裏面
           redoCmds.add(cmd);
           //把相應的備忘錄對象也添加過去
           redoMementos.add(ms);
          
           //而後把最後一個命令刪除掉,
           undoCmds.remove(cmd);
           //把相應的備忘錄對象也刪除掉
           undoMementos.remove(ms);
       }else{
           System.out.println("很抱歉,沒有可撤銷的命令");
       }
    }
    public void redoPressed(){
       if(redoCmds.size()>0){
           //取出最後一個命令來重作
           Command cmd = redoCmds.get(redoCmds.size()-1);
           //獲取對應的備忘錄對象
           Memento[] ms = redoMementos.get(redoCmds.size()-1);
          
           //重作
           cmd.redo(ms[1]);
          
           //把這個命令記錄到可撤銷的歷史記錄裏面
           undoCmds.add(cmd);
           //把相應的備忘錄對象也添加過去
           undoMementos.add(ms);
           //而後把最後一個命令刪除掉
           redoCmds.remove(cmd);
           //把相應的備忘錄對象也刪除掉
           redoMementos.remove(ms);
       }else{
           System.out.println("很抱歉,沒有可恢復的命令");
       }
    }
}

(8)客戶端跟之前的實現沒有什麼變化,示例代碼以下:

public class Client {
    public static void main(String[] args) {
       //1:組裝命令和接收者
       //建立接收者
       OperationApi operation = new Operation();
       //建立命令
       AddCommand addCmd = new AddCommand(5);
       SubstractCommand substractCmd = new SubstractCommand(3);
       //組裝命令和接收者
       addCmd.setOperation(operation);
       substractCmd.setOperation(operation);
      
       //2:把命令設置到持有者,就是計算器裏面
       Calculator calculator = new Calculator();
       calculator.setAddCmd(addCmd);
       calculator.setSubstractCmd(substractCmd);
      
       //3:模擬按下按鈕,測試一下
       calculator.addPressed();
       System.out.println("一次加法運算後的結果爲:"
+operation.getResult());
       calculator.substractPressed();
       System.out.println("一次減法運算後的結果爲:"
+operation.getResult());
      
       //測試撤消
       calculator.undoPressed();
       System.out.println("撤銷一次後的結果爲:"
+operation.getResult());
       calculator.undoPressed();
       System.out.println("再撤銷一次後的結果爲:"
+operation.getResult());
      
       //測試恢復
       calculator.redoPressed();
       System.out.println("恢復操做一次後的結果爲:"
+operation.getResult());
       calculator.redoPressed();
       System.out.println("再恢復操做一次後的結果爲:"
+operation.getResult());
    }
}

運行結果,示例以下:示例代碼以下:

一次加法運算後的結果爲:5
一次減法運算後的結果爲:2
撤銷一次後的結果爲:5
再撤銷一次後的結果爲:0
恢復操做一次後的結果爲:5
再恢復操做一次後的結果爲:2

跟前面採用補償式或者反操做式獲得的結果是同樣的。好好體會一下,對比兩種實現方式,看看都是怎麼實現的。順便也體會一下命令模式和備忘錄模式是如何結合起來實現功能的。

備忘錄模式的優缺點

   更好的封裝性;
      備忘錄模式經過使用備忘錄對象,來封裝原發器對象的內部狀態,雖然這個對象是保存在原發器對象的外部,可是因爲備忘錄對象的窄接口並不提供任何方法,這樣有效的保證了對原發器對象內部狀態的封裝,不把原發器對象的內部實現細節暴露給外部。

   簡化了原發器
     備忘錄模式中,備忘錄對象被保存到原發器對象以外,讓客戶來管理他們請求的狀態,從而讓原發器對象獲得簡化。

  窄接口和寬接口
     備忘錄模式,經過引入窄接口和寬接口,使得不一樣的地方,對備忘錄對象的訪問是不同的。窄接口保證了只有原發器才能夠訪問備忘錄對象的狀態。

   可能會致使高開銷
     備忘錄模式基本的功能,就是對備忘錄對象的存儲和恢復,它的基本實現方式就是緩存備忘錄對象。這樣一來,若是須要緩存的數據量很大,或者是特別頻繁的建立備忘錄對象,開銷是很大的。


相關模式

 備忘錄模式和命令模式
    這兩個模式能夠組合使用。
    命令模式實現中,在實現命令的撤銷和重作的時候,可使用備忘錄模式,在命令操做的時候記錄下操做先後的狀態,而後在命令撤銷和重作的時候,直接使用相應的備忘錄對象來恢復狀態就能夠了。
    在這種撤銷的執行順序和重作執行順序可控的狀況下,備忘錄對象還能夠採用增量式記錄的方式,能夠減小緩存的數據量。

  備忘錄模式和原型模式
    這兩個模式能夠組合使用。
    在原發器對象建立備忘錄對象的時候,若是原發器對象中所有或者大部分的狀態都須要保存,一個簡潔的方式就是直接克隆一個原發器對象。也就是說,這個時候備忘錄對象裏面存放的是一個原發器對象的實例



轉載至:http://sishuok.com/forum/blogPost/list/5633.html

   cc老師的設計模式是我目前看過最詳細最有實踐的教程。

相關文章
相關標籤/搜索