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

3.4  宏命令

        什麼是宏命令呢?簡單點說就是包含多個命令的命令,是一個命令的組合。舉個例子來說吧,設想一下你去飯店喫飯的過程:
        (1)你走進一家飯店,找到座位坐下
        (2)服務員走過來,遞給你菜譜
        (3)你開始點菜,服務員開始記錄菜單,菜單是三聯的,點菜完畢,服務員就會把菜單分成三份,一份給後廚,一份給收銀臺,一份保留備查。
        (4)點完菜,你坐在座位上等候,後廚會按照菜單做菜
        (5)每做好一份菜,就會由服務員送到你桌子上
        (6)然後你就可以大快朵頤了
        事實上,到飯店點餐是一個很典型的命令模式應用,作爲客戶的你,只需要發出命令,就是要喫什麼菜,每道菜就相當於一個命令對象,服務員會在菜單上記錄你點的菜,然後把菜單傳遞給後廚,後廚拿到菜單,會按照菜單進行飯菜製作,後廚就相當於接收者,是命令的真正執行者,廚師才知道每道菜具體怎麼實現。
        在這個過程中,地位比較特殊的是服務員,在不考慮更復雜的管理,比如後廚管理的時候,負責命令和接收者的組裝的就是服務員。比如你點了涼菜、熱菜,你其實是不知道到底涼菜由誰來完成,熱菜由誰來完成的,因此你只管發命令,而組裝的工作就由服務員完成了,服務員知道涼菜送到涼菜部,那是已經做好的了,熱菜才送到後廚,需要廚師現做,看起來服務員是一個組裝者。
        同時呢,服務員還持有命令對象,也就是菜單,最後啓動命令執行的也是服務員。因此,服務員就相當於標準命令模式中的Client和Invoker的融合。
        畫個圖來描述上述對應關係,如圖6所示:


                                                   圖6  點菜行爲與命令模式對應示意圖
1:宏命令在哪裏?
        仔細觀察上面的過程,再想想前面的命令模式的實現,看出點什麼沒有?
        前面實現的命令模式,都是客戶端發出一個命令,然後馬上就執行了這個命令,但是在上面的描述裏面呢?是點一個菜,服務員就告訴廚師,然後廚師就開始做嗎?很明顯不是的,服務員會一直等,等到你點完菜,當你說“點完了”的時候,服務員纔會啓動命令的執行,請注意,這個時候執行的就不是一個命令了,而是執行一堆命令。
        描述這一堆命令的就是菜單,如果把菜單也抽象成爲一個命令,就相當於一個大的命令,當客戶說“點完了”的時候,就相當於觸發這個大的命令,意思就是執行菜單這個命令就可以了,這個菜單命令包含多個命令對象,一個命令對象就相當於一道菜。
        那麼這個菜單就相當於我們說的宏命令。
2:如何實現宏命令
        宏命令從本質上講類似於一個命令,基本上把它當命令對象進行處理。但是它跟普通的命令對象又有些不一樣,就是宏命令包含有多個普通的命令對象,執行一個宏命令,簡單點說,就是執行宏命令裏面所包含的所有命令對象,有點打包執行的意味。
(1)先來定義接收者,就是廚師的接口和實現,先看接口,示例代碼如下:

  1. /** 
  2.  * 廚師的接口 
  3.  */  
  4. public interface CookApi {  
  5.     /** 
  6.      * 示意,做菜的方法 
  7.      * @param name 菜名 
  8.      */  
  9.     public void cook(String name);  
  10. }  

 

廚師又分成兩類,一類是做熱菜的師傅,一類是做涼菜的師傅,先看看做熱菜的廚師的實現示意,示例代碼如下:

  1. /** 
  2.  * 廚師對象,做熱菜 
  3.  */  
  4. public class HotCook implements CookApi{  
  5.     public void cook(String name) {  
  6.         System.out.println("本廚師正在做:"+name);  
  7.     }  
  8. }  

 

做涼菜的師傅,示例代碼如下:

  1. /** 
  2.  * 廚師對象,做涼菜 
  3.  */  
  4. public class CoolCook implements CookApi {  
  5.     public void cook(String name) {  
  6.         System.out.println("涼菜"+name+"已經做好,本廚師正在裝盤。" );  
  7.     }  
  8. }  

 

(2)接下來,來定義命令接口,跟以前一樣,示例代碼如下:

  1. /** 
  2.  * 命令接口,聲明執行的操作 
  3.  */  
  4. public interface Command {  
  5.     /** 
  6.      * 執行命令對應的操作 
  7.      */  
  8.     public void execute();  
  9. }  

 

(3)定義好了命令的接口,該來具體實現命令了。
        實現方式跟以前一樣,持有接收者,當執行命令的時候,轉調接收者,讓接收者去真正實現功能,這裏的接收者就是廚師。 
        這裏實現命令的時候,跟標準的命令模式的命令實現有一點不同,標準的命令模式的命令實現的時候,是通過構造方法傳入接收者對象,這裏改成了使用setter的方式來設置接收者對象,也就是說可以動態的切換接收者對象,而無須重新構建對象。
        示例中定義了三道菜,分別是兩道熱菜:北京烤鴨、綠豆排骨煲,一道涼菜:蒜泥白肉,三個具體的實現類非常類似,只是菜名不同,爲了節省篇幅,這裏就只看一個命令對象的具體實現。代碼示例如下:

  1. /** 
  2.  * 命令對象,綠豆排骨煲 
  3.  */  
  4. public class ChopCommand implements Command{  
  5.     /** 
  6.      * 持有具體做菜的廚師的對象 
  7.      */  
  8.     private CookApi cookApi = null;  
  9.     /** 
  10.      * 設置具體做菜的廚師的對象 
  11.      * @param cookApi 具體做菜的廚師的對象 
  12.      */  
  13.     public void setCookApi(CookApi cookApi) {  
  14.         this.cookApi = cookApi;  
  15.     }  
  16.   
  17.     public void execute() {  
  18.         this.cookApi.cook("綠豆排骨煲");  
  19.     }  
  20. }  

 

(4)該來組合菜單對象了,也就是宏命令對象。

  • 首先宏命令就其本質還是一個命令,所以一樣要實現Command接口
  • 其次宏命令跟普通命令的不同在於:宏命令是多個命令組合起來的,因此在宏命令對象裏面會記錄多個組成它的命令對象
  • 第三,既然是包含多個命令對象,得有方法讓這多個命令對象能被組合進來
  • 第四,既然宏命令包含了多個命令對象,執行宏命令對象就相當於依次執行這些命令對象,也就是循環執行這些命令對象

     看看代碼示例會更清晰些,代碼示例如下:

  1. /** 
  2.  * 菜單對象,是個宏命令對象 
  3.  */  
  4. public class MenuCommand implements Command {  
  5.     /** 
  6.      * 用來記錄組合本菜單的多道菜品,也就是多個命令對象 
  7.      */  
  8.     private Collection<Command> col = new ArrayList<Command>();  
  9.     /** 
  10.      * 點菜,把菜品加入到菜單中 
  11.      * @param cmd 客戶點的菜 
  12.      */  
  13.     public void addCommand(Command cmd){  
  14.         col.add(cmd);  
  15.     }  
  16.     public void execute() {  
  17.         //執行菜單其實就是循環執行菜單裏面的每個菜  
  18.         for(Command cmd : col){  
  19.             cmd.execute();  
  20.         }  
  21.     }  
  22. }  

(5)該服務員類重磅登場了,它實現的功能,相當於標準命令模式實現中的Client加上Invoker,前面都是文字講述,看看代碼如何實現,示例代碼如下:

  1. /** 
  2.  * 服務員,負責組合菜單,負責組裝每個菜和具體的實現者, 
  3.  * 還負責執行調用,相當於標準Command模式的Client+Invoker 
  4.  */  
  5. public class Waiter {  
  6.     /** 
  7.      * 持有一個宏命令對象——菜單 
  8.      */  
  9.     private MenuCommand menuCommand = new MenuCommand();  
  10.     /** 
  11.      * 客戶點菜 
  12.      * @param cmd 客戶點的菜,每道菜是一個命令對象 
  13.      */  
  14.     public void orderDish(Command cmd){  
  15.         //客戶傳過來的命令對象是沒有和接收者組裝的  
  16.         //在這裏組裝吧  
  17.         CookApi hotCook = new HotCook();  
  18.         CookApi coolCook = new CoolCook();  
  19.         //判讀到底是組合涼菜師傅還是熱菜師傅  
  20.         //簡單點根據命令的原始對象的類型來判斷  
  21.         if(cmd instanceof DuckCommand){  
  22.             ((DuckCommand)cmd).setCookApi(hotCook);  
  23.         }else if(cmd instanceof ChopCommand){  
  24.             ((ChopCommand)cmd).setCookApi(hotCook);  
  25.         }else if(cmd instanceof PorkCommand){  
  26.             //這是個涼菜,所以要組合涼菜的師傅  
  27.             ((PorkCommand)cmd).setCookApi(coolCook);  
  28.         }  
  29.         //添加到菜單中  
  30.         menuCommand.addCommand(cmd);  
  31.     }  
  32.     /** 
  33.      * 客戶點菜完畢,表示要執行命令了,這裏就是執行菜單這個組合命令 
  34.      */  
  35.     public void orderOver(){  
  36.         this.menuCommand.execute();  
  37.     }  
  38. }  

(6)費了這麼大力氣,終於可以坐下來歇息一下,點菜喫飯吧,一起來看看客戶端怎麼使用這個宏命令,其實在客戶端非常簡單,根本看不出宏命令來,代碼示例如下:

  1. public class Client {  
  2.     public static void main(String[] args) {  
  3.         //客戶只是負責向服務員點菜就好了  
  4.         //創建服務員  
  5.         Waiter waiter = new Waiter();  
  6.           
  7.         //創建命令對象,就是要點的菜  
  8.         Command chop = new ChopCommand();  
  9.         Command duck = new DuckCommand();  
  10.         Command pork = new PorkCommand();  
  11.           
  12.         //點菜,就是把這些菜讓服務員記錄下來  
  13.         waiter.orderDish(chop);  
  14.         waiter.orderDish(duck);  
  15.         waiter.orderDish(pork);  
  16.           
  17.         //點菜完畢  
  18.         waiter.orderOver();  
  19.     }  
  20. }  

運行一下,享受一下成果,結果如下:

  1. 本廚師正在做:綠豆排骨煲  
  2. 本廚師正在做:北京烤鴨  
  3. 涼菜蒜泥白肉已經做好,本廚師正在裝盤。  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章