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

3.7  命令模式的優缺點

  • 更鬆散的耦合
        命令模式使得發起命令的對象——客戶端,和具體實現命令的對象——接收者對象完全解耦,也就是說發起命令的對象,完全不知道具體實現對象是誰,也不知道如何實現。
  • 更動態的控制
        命令模式把請求封裝起來,可以動態對它進行參數化、隊列化和日誌化等操作,從而使得系統更靈活。
  • 能很自然的複合命令
        命令模式中的命令對象,能夠很容易的組合成爲複合命令,就是前面講的宏命令,從而使系統操作更簡單,功能更強大。
  • 更好的擴展性
        由於發起命令的對象和具體的實現完全解耦,因此擴展新的命令就很容易,只需要實現新的命令對象,然後在裝配的時候,把具體的實現對象設置到命令對象裏面,然後就可以使用這個命令對象,已有的實現完全不用變化。

 

3.8  思考命令模式

1:命令模式的本質
        命令模式的本質:封裝請求
        前面講了,命令模式的關鍵就是把請求封裝成爲命令對象,然後就可以對這個對象進行一系列的處理了,比如上面講到的參數化配置、可撤銷操作、宏命令、隊列請求、日誌請求等功能處理。
2:何時選用命令模式
        建議在如下情況中,選用命令模式:

  • 如果需要抽象出需要執行的動作,並參數化這些對象,可以選用命令模式,把這些需要執行的動作抽象成爲命令,然後實現命令的參數化配置
  • 如果需要在不同的時刻指定、排列和執行請求,可以選用命令模式,把這些請求封裝成爲命令對象,然後實現把請求隊列化
  • 如果需要支持取消操作,可以選用命令模式,通過管理命令對象,能很容易的實現命令的恢復和重做的功能
  • 如果需要支持當系統崩潰時,能把對系統的操作功能重新執行一遍,可以選用命令模式,把這些操作功能的請求封裝成命令對象,然後實現日誌命令,就可以在系統恢復回來後,通過日誌獲取命令列表,從而重新執行一遍功能
  • 在需要事務的系統中,可以選用命令模式,命令模式提供了對事務進行建模的方法,命令模式有一個別名就是Transaction。

 

3.9  退化的命令模式

        在領會了命令模式本質後,來思考一個命令模式退化的情況。
        前面講到了智能命令,如果命令的實現對象超級智能,實現了命令所要求的功能,那麼就不需要接收者了,既然沒有了接收者,那麼也就不需要組裝者了。
(1)舉個最簡單的示例來說明
        比如現在要實現一個打印服務,由於非常簡單,所以基本上就沒有什麼講述,依次來看,命令接口定義如下:

  1. public interface Command {  
  2.     public void execute();  
  3. }  

 

命令的實現示例代碼如下:

  1. public class PrintService implements Command{  
  2.     /** 
  3.      * 要輸出的內容 
  4.      */  
  5.     private String str = "";  
  6.     /** 
  7.      * 構造方法,傳入要輸出的內容 
  8.      * @param s 要輸出的內容 
  9.      */  
  10.     public PrintService(String s){  
  11.         str = s;  
  12.     }  
  13.     public void execute() {  
  14.                                 //智能的體現,自己知道怎麼實現命令所要求的功能,並真的實現了相應的功能,不再轉調接收者了  
  15.         System.out.println("打印的內容爲="+str);  
  16.     }  
  17. }  

 

此時的Invoker示例代碼如下:

  1. public class Invoker {  
  2.     /** 
  3.      * 持有命令對象 
  4.      */  
  5.     private Command cmd = null;  
  6.     /** 
  7.      * 設置命令對象 
  8.      * @param cmd 命令對象 
  9.      */  
  10.     public void setCmd(Command cmd){  
  11.         this.cmd = cmd;  
  12.     }  
  13.     /** 
  14.      * 開始打印 
  15.      */  
  16.     public void startPrint(){  
  17.         //執行命令的功能  
  18.         this.cmd.execute();  
  19.     }  
  20. }  

 

最後看看客戶端的代碼,示例如下:

  1. public class Client {  
  2.     public static void main(String[] args) {  
  3.         //準備要發出的命令  
  4.         Command cmd = new PrintService("退化的命令模式示例");  
  5.         //設置命令給持有者  
  6.         Invoker invoker = new Invoker();  
  7.         invoker.setCmd(cmd);      
  8.   
  9.         //按下按鈕,真正啓動執行命令  
  10.         invoker.startPrint();  
  11.     }  
  12. }  

 

測試結果如下:

  1. 打印的內容爲=退化的命令模式示例  

 

(2)繼續變化
        如果此時繼續變化,Invoker也開始變得智能化,在Invoker的startPrint方法裏面,Invoker加入了一些實現,同時Invoker對持有命令也有意見,覺得自己是個傀儡,要求改變一下,直接在調用方法的時候傳遞命令對象進來,示例代碼如下:

  1. public class Invoker {  
  2.     public void startPrint(Command cmd){      
  3.         System.out.println("在Invoker中,輸出服務前");  
  4.         cmd.execute();  
  5.         System.out.println("輸出服務結束");  
  6.     }  
  7. }  

 

        看起來Invoker退化成一個方法了。
        這個時候Invoker很高興,宣稱自己是一個智能的服務,不再是一個傻傻的轉調者,而是有自己功能的服務了。這個時候Invoker調用命令對象的執行方法,也不叫轉調,改名叫“回調”,意思是在我Invoker需要的時候,會回調你命令對象,命令對象你就乖乖的寫好實現,等我“回調”你就可以了。
        事實上這個時候的命令模式的實現,基本上就等同於Java回調機制的實現,可能有些朋友看起來感覺還不是佷像,那是因爲在Java回調機制的常見實現上,經常沒有單獨的接口實現類,而是採用匿名內部類的方式來實現的。

(3)再進一步
        把單獨實現命令接口的類改成用匿名內部類實現,這個時候就只剩下命令的接口、Invoker類,還有客戶端了。
        爲了使用匿名內部類,還要設置要輸出的值,對命令接口做點小改動,增加一個設置輸出值的方法,示例代碼如下:

  1. public interface Command {  
  2.     public void execute();  
  3.     /** 
  4.      * 設置要輸出的內容 
  5.      * @param s 要輸出的內容 
  6.      */  
  7.     public void setStr(String s);  
  8. }  

 

此時Invoker就是上面那個,而客戶端會有些改變,客戶端的示例代碼如下:

  1. public class Client {  
  2.     public static void main(String[] args) {  
  3.         //準備要發出的命令,沒有具體實現類了  
  4.                     //匿名內部類來實現命令   
  5.         Command cmd = new Command(){  
  6.             private String str = "";  
  7.             public void setStr(String s){  
  8.                 str = s;  
  9.             }  
  10.             public void execute() {  
  11.                 System.out.println("打印的內容爲="+str);  
  12.             }  
  13.         };  
  14.         cmd.setStr("退化的命令模式類似於Java回調的示例");        
  15.         //這個時候的Invoker或許該稱爲服務了  
  16.         Invoker invoker = new Invoker();  
  17.         //按下按鈕,真正啓動執行命令  
  18.         invoker.startPrint(cmd);  
  19.     }  
  20. }  

 

運行測試一下,結果如下:

  1. 在Invoker中,輸出服務前  
  2. 打印的內容爲=退化的命令模式類似於Java回調的示例  
  3. 輸出服務結束  

 

(4)現在是不是看出來了,這個時候的命令模式的實現,基本上就等同於Java回調機制的實現。這也是很多人大談特談命令模式可以實現Java回調的意思。
        當然更狠的是連Invoker也不要了,直接把那個方法搬到Client中,那樣測試起來就更方便了。在實際開發中,應用命令模式來實現回調機制的時候,Invoker通常還是有的,但可以智能化實現,更準確的說Invoker充當客戶調用的服務實現,而回調的方法只是實現服務功能中的一個或者幾個步驟。

3.10  相關模式

  • 命令模式和組合模式
        這兩個模式可以組合使用。
        在命令模式中,實現宏命令的功能,就可以使用組合模式來實現。前面的示例並沒有按照組合模式來做,那是爲了保持示例的簡單,還有突出命令模式的實現,這點請注意。
  • 命令模式和備忘錄模式
        這兩個模式可以組合使用。
        在命令模式中,實現可撤銷操作功能時,前面講了有兩種實現方式,其中有一種就是保存命令執行前的狀態,撤銷的時候就把狀態恢復回去。如果採用這種方式實現,就可以考慮使用備忘錄模式。
        如果狀態存儲在命令對象裏面,那麼還可以使用原型模式,把命令對象當作原型來克隆一個新的對象,然後把克隆出來的對象通過備忘錄模式存放。
  • 命令模式和模板方法模式
        這兩個模式從某種意義上有相似的功能,命令模式可以作爲模板方法的一種替代模式,也就是說命令模式可以模仿實現模板方法模式的功能。
        如同前面講述的退化的命令模式可以實現Java的回調,而Invoker智能化後向服務進化,如果Invoker的方法就是一個算法骨架,其中有兩步在這個骨架裏面沒有具體實現,需要外部來實現,這個時候就可以通過回調命令接口來實現。
        而類似的功能在模板方法裏面,一個算法骨架,其中有兩步在這個骨架裏面沒有具體實現,是先調用抽象方法,然後等待子類來實現。
        可以看出雖然實現方式不一樣,但是可以實現相同的功能。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章