23種設計模式之命令模式3

3.3  可撤銷的操作

        可撤銷操作的意思就是:放棄該操作,回到未執行該操作前的狀態。這個功能是一個非常重要的功能,幾乎所有GUI應用裏面都有撤消操作的功能。GUI的菜單是命令模式最典型的應用之一,所以你總是能在菜單上找到撤銷這樣的菜單項。
        既然這麼常用,那該如何實現呢?
        有兩種基本的思路來實現可撤銷的操作,一種是補償式,又稱反操作式:比如被撤銷的操作是加的功能,那撤消的實現就變成減的功能;同理被撤銷的操作是打開的功能,那麼撤銷的實現就變成關閉的功能。
        另外一種方式是存儲恢復式,意思就是把操作前的狀態記錄下來,然後要撤銷操作的時候就直接恢復回去就可以了。
        這裏先講第一種方式,就是補償式或者反操作式,第二種方式放到備忘錄模式中去講解。爲了讓大家更好的理解可撤銷操作的功能,還是用一個例子來說明會比較清楚。
1:範例需求
        考慮一個計算器的功能,最簡單的那種,只能實現加減法運算,現在要讓這個計算器支持可撤銷的操作。
2:補償式或者反操作式的解決方案
(1)在實現命令接口之前,先來定義真正實現計算的接口,沒有它命令什麼都做不了,操作運算的接口的示例代碼如下:

  1. /** 
  2.  * 操作運算的接口 
  3.  */  
  4. public interface OperationApi {  
  5.     /** 
  6.      * 獲取計算完成後的結果 
  7.      * @return 計算完成後的結果 
  8.      */  
  9.     public int getResult();  
  10.     /** 
  11.      * 設置計算開始的初始值 
  12.      * @param result 計算開始的初始值 
  13.      */  
  14.     public void setResult(int result);  
  15.     /** 
  16.      * 執行加法 
  17.      * @param num 需要加的數 
  18.      */  
  19.     public void add(int num);  
  20.     /** 
  21.      * 執行減法 
  22.      * @param num 需要減的數 
  23.      */  
  24.     public void substract(int num);  
  25. }  

 

定義了接口,來看看真正執行加減法的實現,示例代碼如下:

  1. /** 
  2.  * 運算類,真正實現加減法運算 
  3.  */  
  4. public class Operation implements OperationApi{  
  5.     /** 
  6.      * 記錄運算的結果 
  7.      */  
  8.     private int result;  
  9.     public int getResult() {  
  10.         return result;  
  11.     }  
  12.     public void setResult(int result) {  
  13.         this.result = result;  
  14.     }     
  15.     <strong>public void add(int num){ //實現加法 功能 result += num; } public void substract(int num){ //實現減法 功能 result -= num; }</strong>  
  16. }  

(2)接下來,來抽象命令接口,由於要支持可撤銷的功能,所以除了跟前面一樣定義一個執行方法外,還需要定義一個撤銷操作的方法,示例代碼如下:

  1. /** 
  2.  * 命令接口,聲明執行的操作,支持可撤銷操作 
  3.  */  
  4. public interface Command {  
  5.     /** 
  6.      * 執行命令對應的操作 
  7.      */  
  8.     public void execute();  
  9.     /** 
  10.      * 執行撤銷命令對應的操作 
  11.      */  
  12.     public void undo();  
  13. }  

(3)應該來實現命令了,具體的命令分成了加法命令和減法命令,先來看看加法命令的實現,示例代碼如下:

  1. /** 
  2.  * 具體的加法命令實現對象 
  3.  */  
  4. public class AddCommand implements Command{  
  5.     /** 
  6.      * 持有具體執行計算的對象 
  7.      */  
  8.     private OperationApi operation = null;  
  9.     /** 
  10.      * 操作的數據,也就是要加上的數據 
  11.      */  
  12.     private int opeNum;  
  13.       
  14.     <strong>public void execute() { //轉調接收者去真正執行功能,這個命令是做加法 this.operation.add(opeNum); } public void undo() { //轉調接收者去真正執行功能 //命令本身是做加法,那麼撤銷的時候就是做減法了 this.operation.substract(opeNum); }</strong>  
  15.               /** 
  16.      * 構造方法,傳入具體執行計算的對象 
  17.      * @param operation 具體執行計算的對象 
  18.      * @param opeNum 要加上的數據 
  19.      */  
  20.     public AddCommand(OperationApi operation,int opeNum){  
  21.         this.operation = operation;  
  22.         this.opeNum = opeNum;  
  23.     }  
  24. }  

 

減法命令和加法類似,只是在實現的時候和加法反過來了,示例代碼如下:

  1. /** 
  2.  * 具體的減法命令實現對象 
  3.  */  
  4. public class SubstractCommand implements Command{  
  5.     /** 
  6.      * 持有具體執行計算的對象 
  7.      */  
  8.     private OperationApi operation = null;  
  9.     /** 
  10.      * 操作的數據,也就是要減去的數據 
  11.      */  
  12.     private int opeNum;  
  13.     /** 
  14.      * 構造方法,傳入具體執行計算的對象 
  15.      * @param operation 具體執行計算的對象 
  16.      * @param opeNum 要減去的數據 
  17.      */  
  18.     public SubstractCommand(OperationApi operation,int opeNum){  
  19.         this.operation = operation;  
  20.         this.opeNum = opeNum;  
  21.     }     
  22.       
  23.     <strong>public void execute() { //轉調接收者去真正執行功能,這個命令是做減法 this.operation.substract(opeNum); } public void undo() { //轉調接收者去真正執行功能 //命令本身是做減法,那麼撤銷的時候就是做加法了 this.operation.add(opeNum); }</strong>  
  24. }  

(4)接下來應該看看計算器了,計算器就相當於Invoker,持有多個命令對象,計算器是實現可撤銷操作的地方。
爲了大家更好的理解可撤銷的功能,先來看看不加可撤銷操作的計算器類什麼樣子,然後再添加上可撤銷的功能示例。示例代碼如下:

  1. /** 
  2.  * 計算器類,計算器上有加法按鈕、減法按鈕 
  3.  */  
  4. public class Calculator {  
  5.     /** 
  6.      * 持有執行加法的命令對象 
  7.      */  
  8.     private Command addCmd = null;  
  9.     /** 
  10.      * 持有執行減法的命令對象 
  11.      */  
  12.     private Command substractCmd = null;  
  13.     /** 
  14.      * 設置執行加法的命令對象 
  15.      * @param addCmd 執行加法的命令對象 
  16.      */  
  17.     public void setAddCmd(Command addCmd) {  
  18.         this.addCmd = addCmd;  
  19.     }  
  20.     /** 
  21.      * 設置執行減法的命令對象 
  22.      * @param substractCmd 執行減法的命令對象 
  23.      */  
  24.     public void setSubstractCmd(Command substractCmd) {  
  25.         this.substractCmd = substractCmd;  
  26.     }     
  27.     /** 
  28.      * 提供給客戶使用,執行加法 功能 
  29.      */  
  30.     public void addPressed(){  
  31.         this.addCmd.execute();  
  32.     }  
  33.     /** 
  34.      * 提供給客戶使用,執行減法 功能 
  35.      */  
  36.     public void substractPressed(){  
  37.         this.substractCmd.execute();  
  38.     }  
  39. }  

 

        目前看起來跟前面的例子實現得差不多,現在就在這個基本的實現上來添加可撤銷操作的功能。
要想實現可撤銷操作,首先就需要把操作過的命令記錄下來,形成命令的歷史列表,撤銷的時候就從最後一個開始執行撤銷。因此我們先在計算器類裏面加上命令歷史列表,示例代碼如下:

  1. /** 
  2. * 命令的操作的歷史記錄,在撤銷時候用 
  3. */  
  4. private List<Command> undoCmds = new ArrayList<Command>();  

 

什麼時候向命令的歷史記錄裏面加值呢?
        很簡單,答案是在每個操作按鈕被按下的時候,也就是你操作加法按鈕或者減法按鈕的時候,示例代碼如下

  1.        public void addPressed(){  
  2.     this.addCmd.execute();  
  3.     //把操作記錄到歷史記錄裏面  
  4.     undoCmds.add(this.addCmd);  
  5. }  
  6. public void substractPressed(){  
  7.     this.substractCmd.execute();  
  8.     //把操作記錄到歷史記錄裏面  
  9.     undoCmds.add(this.substractCmd);  
  10. }  

 

        然後在計算器類裏面添加上一個撤銷的按鈕,如果它被按下,那麼就從命令歷史記錄裏取出最後一個命令來撤銷,撤消完成後要把已經撤銷的命令從歷史記錄裏面刪除掉,相當於沒有執行過該命令了,示例代碼如下:

  1. public void undoPressed(){  
  2.     if(this.undoCmds.size()>0){  
  3.         //取出最後一個命令來撤銷  
  4.         Command cmd = this.undoCmds.get(this.undoCmds.size()-1);  
  5.         cmd.undo();  
  6.         //然後把最後一個命令刪除掉,  
  7.         this.undoCmds.remove(cmd);  
  8.     }else{  
  9.         System.out.println("很抱歉,沒有可撤銷的命令");  
  10.     }  
  11. }  

 

        同樣的方式,還可以實現恢復的功能,也爲恢復設置一個可恢復的列表,需要恢復的時候從列表裏面取最後一個命令進行重新執行就好了,示例代碼如下:

  1. /** 
  2. * 命令被撤銷的歷史記錄,在恢復時候用 
  3. */  
  4. private List<Command> redoCmds = new ArrayList<Command>();  

 

        那麼什麼時候向這個集合裏面賦值呢?大家要注意,恢復的命令數據是來源於撤銷的命令,也就是說有撤銷纔會有恢復,所以在撤銷的時候向這個集合裏面賦值,注意要在撤銷的命令被刪除前賦值。示例代碼如下:

  1. public void undoPressed(){  
  2.     if(this.undoCmds.size()>0){  
  3.         //取出最後一個命令來撤銷  
  4.         Command cmd = this.undoCmds.get(this.undoCmds.size()-1);  
  5.         cmd.undo();  
  6.         <strong>//如果還有恢復的功能,那就把這個命令記錄到恢復的歷史記錄裏面 this.redoCmds.add(cmd);</strong>  
  7.         //然後把最後一個命令刪除掉,  
  8.         this.undoCmds.remove(cmd);  
  9.     }else{  
  10.         System.out.println("很抱歉,沒有可撤銷的命令");  
  11.     }  
  12. }  

 

         那麼如何實現恢復呢?請看示例代碼:

  1. public void redoPressed(){  
  2.     if(this.redoCmds.size()>0){  
  3.         //取出最後一個命令來重做  
  4.         Command cmd = this.redoCmds.get(this.redoCmds.size()-1);  
  5.         cmd.execute();        
  6.         <strong>//把這個命令記錄到可撤銷的歷史記錄裏面 this.undoCmds.add(cmd);</strong>  
  7.         //然後把最後一個命令刪除掉  
  8.         this.redoCmds.remove(cmd);  
  9.     }else{  
  10.         System.out.println("很抱歉,沒有可恢復的命令");  
  11.     }  
  12. }  

 

好了,分步講解了計算器類,一起來看看完整的計算器類的代碼:

  1. /** 
  2.  * 計算器類,計算器上有加法按鈕、減法按鈕,還有撤銷和恢復的按鈕 
  3.  */  
  4. public class Calculator {  
  5.     /** 
  6.      * 命令的操作的歷史記錄,在撤銷時候用 
  7.      */  
  8.     private List<Command> undoCmds = new ArrayList<Command>();  
  9.     /** 
  10.      * 命令被撤銷的歷史記錄,在恢復時候用 
  11.      */  
  12.     private List<Command> redoCmds = new ArrayList<Command>();  
  13.       
  14.     private Command addCmd = null;  
  15.     private Command substractCmd = null;  
  16.     public void setAddCmd(Command addCmd) {  
  17.         this.addCmd = addCmd;  
  18.     }  
  19.     public void setSubstractCmd(Command substractCmd) {  
  20.         this.substractCmd = substractCmd;  
  21.     }     
  22.     public void addPressed(){  
  23.         this.addCmd.execute();  
  24.         //把操作記錄到歷史記錄裏面  
  25.         undoCmds.add(this.addCmd);  
  26.     }  
  27.     public void substractPressed(){  
  28.         this.substractCmd.execute();  
  29.         //把操作記錄到歷史記錄裏面  
  30.         undoCmds.add(this.substractCmd);  
  31.     }  
  32.     public void undoPressed(){  
  33.         if(this.undoCmds.size()>0){  
  34.             //取出最後一個命令來撤銷  
  35.             Command cmd = this.undoCmds.get(undoCmds.size()-1);  
  36.             cmd.undo();  
  37.             //如果還有恢復的功能,那就把這個命令記錄到恢復的歷史記錄裏面  
  38.             this.redoCmds.add(cmd );  
  39.             //然後把最後一個命令刪除掉,  
  40.             this.undoCmds.remove(cmd);  
  41.         }else{  
  42.             System.out.println("很抱歉,沒有可撤銷的命令");  
  43.         }  
  44.     }  
  45.     public void redoPressed(){  
  46.         if(this.redoCmds.size()>0){  
  47.             //取出最後一個命令來重做  
  48.             Command cmd = this.redoCmds.get(redoCmds.size()-1);  
  49.             cmd.execute();        
  50.             //把這個命令記錄到可撤銷的歷史記錄裏面  
  51.             this.undoCmds.add(cmd);  
  52.             //然後把最後一個命令刪除掉  
  53.             this.redoCmds.remove(cmd);  
  54.         }else{  
  55.             System.out.println("很抱歉,沒有可恢復的命令");  
  56.         }  
  57.     }  
  58. }  

 

(5)終於到可以收穫的時候了,寫個客戶端,組裝好命令和接收者,然後操作幾次命令,來測試一下撤銷和恢復的功能,示例代碼如下:

  1. public class Client {  
  2.     public static void main(String[] args) {  
  3.         //1:組裝命令和接收者  
  4.         //創建接收者  
  5.         OperationApi operation = new Operation();  
  6.         //創建命令對象,並組裝命令和接收者  
  7.         AddCommand addCmd = new AddCommand   (operation,5);  
  8.         SubstractCommand substractCmd =   
  9.                                     new SubstractCommand(operation,3);  
  10.           
  11.         //2:把命令設置到持有者,就是計算器裏面  
  12.         Calculator calculator = new Calculator();  
  13.         calculator.setAddCmd(addCmd);  
  14.         calculator.setSubstractCmd(substractCmd);  
  15.           
  16.         //3:模擬按下按鈕,測試一下  
  17.         calculator.addPressed();  
  18.         System.out.println("一次加法運算後的結果爲:"  
  19.                                      +operation.getResult());  
  20.         calculator.substractPressed();  
  21.         System.out.println("一次減法運算後的結果爲:"  
  22.                                      +operation.getResult());  
  23.           
  24.         //測試撤消  
  25.         calculator.undoPressed();  
  26.         System.out.println("撤銷一次後的結果爲:"  
  27.                                      +operation.getResult());  
  28.         calculator.undoPressed();  
  29.         System.out.println("再撤銷一次後的結果爲:"  
  30.                                      +operation.getResult());  
  31.           
  32.         //測試恢復  
  33.         calculator.redoPressed();  
  34.         System.out.println("恢復操作一次後的結果爲:"  
  35.                                      +operation.getResult());  
  36.         calculator.redoPressed();  
  37.         System.out.println("再恢復操作一次後的結果爲:"  
  38.                                      +operation.getResult());  
  39.     }  
  40. }  

(6)運行一下,看看結果,享受一下可以撤銷和恢復的操作,結果如下:

  1. 一次加法運算後的結果爲:5  
  2. 一次減法運算後的結果爲:2  
  3. 撤銷一次後的結果爲:5  
  4. 再撤銷一次後的結果爲:0  
  5. 恢復操作一次後的結果爲:5  
  6. 再恢復操作一次後的結果爲:2  

 

也就是初始值爲0,執行的兩次命令操作爲先加上5,然後再減去3。看起來也很容易,對不。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章