1. 程式人生 > >命令模式(二)

命令模式(二)

設計模式 命令模式

宏命令

什麽是宏命令呢?簡單點說就是包含多個命令的命令,是一個命令的組合。舉個例子來說吧,設想一下你去飯店吃飯的過程:
(1)你走進一家飯店,找到座位坐下
(2)服務員走過來,遞給你菜譜
(3)你開始點菜,服務員開始記錄菜單,菜單是三聯的,點菜完畢,服務員就會把菜單分成三份,一份給後廚,一份給收銀臺,一份保留備查。
(4)點完菜,你坐在座位上等候,後廚會按照菜單做菜
(5)每做好一份菜,就會由服務員送到你桌子上
(6)然後你就可以大快朵頤了
事實上,到飯店點餐是一個很典型的命令模式應用,作為客戶的你,只需要發出命令,就是要吃什麽菜,每道菜就相當於一個命令對象,服務員會在菜單上記錄你點的菜,然後把菜單傳遞給後廚,後廚拿到菜單,會按照菜單進行飯菜制作,後廚就相當於接收者,是命令的真正執行者,廚師才知道每道菜具體怎麽實現。

在這個過程中,地位比較特殊的是服務員,在不考慮更復雜的管理,比如後廚管理的時候,負責命令和接收者的組裝的就是服務員。比如你點了涼菜、熱菜,你其實是不知道到底涼菜由誰來完成,熱菜由誰來完成的,因此你只管發命令,而組裝的工作就由服務員完成了,服務員知道涼菜送到涼菜部,那是已經做好的了,熱菜才送到後廚,需要廚師現做,看起來服務員是一個組裝者。
同時呢,服務員還持有命令對象,也就是菜單,最後啟動命令執行的也是服務員。因此,服務員就相當於標準命令模式中的Client和Invoker的融合。
畫個圖來描述上述對應關系,如圖所示:

技術分享

1:宏命令在哪裏?
仔細觀察上面的過程,再想想前面的命令模式的實現,看出點什麽沒有?
前面實現的命令模式,都是客戶端發出一個命令,然後馬上就執行了這個命令,但是在上面的描述裏面呢?是點一個菜,服務員就告訴廚師,然後廚師就開始做嗎?很明顯不是的,服務員會一直等,等到你點完菜,當你說“點完了”的時候,服務員才會啟動命令的執行,請註意,這個時候執行的就不是一個命令了,而是執行一堆命令。
描述這一堆命令的就是菜單,如果把菜單也抽象成為一個命令,就相當於一個大的命令,當客戶說“點完了”的時候,就相當於觸發這個大的命令,意思就是執行菜單這個命令就可以了,這個菜單命令包含多個命令對象,一個命令對象就相當於一道菜。


那麽這個菜單就相當於我們說的宏命令。
2:如何實現宏命令
宏命令從本質上講類似於一個命令,基本上把它當命令對象進行處理。但是它跟普通的命令對象又有些不一樣,就是宏命令包含有多個普通的命令對象,執行一個宏命令,簡單點說,就是執行宏命令裏面所包含的所有命令對象,有點打包執行的意味。
(1)先來定義接收者,就是廚師的接口和實現,先看接口,示例代碼如下:

/** 
 * 廚師的接口 
 */  
public interface CookApi {  
    /** 
     * 示意,做菜的方法 
     * @param name 菜名 
     */  
    public void cook(String name);  
}

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

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

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

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

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

/** 
 * 命令接口,聲明執行的操作 
 */  
public interface Command {  
    /** 
     * 執行命令對應的操作 
     */  
    public void execute();  
}

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

/** 
 * 命令對象,綠豆排骨煲 
 */  
public class ChopCommand implements Command{  
    /** 
     * 持有具體做菜的廚師的對象 
     */  
    private CookApi cookApi = null;  
    /** 
     * 設置具體做菜的廚師的對象 
     * @param cookApi 具體做菜的廚師的對象 
     */  
    public void setCookApi(CookApi cookApi) {  
        this.cookApi = cookApi;  
    }  
  
    public void execute() {  
        this.cookApi.cook("綠豆排骨煲");  
    }  
}
/**
 * 命令對象,北京烤鴨
 */
public class DuckCommand implements Command{
    private CookApi cookApi = null;
    public void setCookApi(CookApi cookApi) {
        this.cookApi = cookApi;
    }
    
    public void execute() {
        this.cookApi.cook("北京烤鴨");
    }
}
/**
 * 命令對象,蒜泥白肉
 */
public class PorkCommand implements Command {
    private CookApi cookApi = null;

    public void setCookApi(CookApi cookApi) {
        this.cookApi = cookApi;
    }

    
    public void execute() {
        this.cookApi.cook("蒜泥白肉");
    }
}

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

  • 首先宏命令就其本質還是一個命令,所以一樣要實現Command接口

  • 其次宏命令跟普通命令的不同在於:宏命令是多個命令組合起來的,因此在宏命令對象裏面會記錄多個組成它的命令對象

  • 第三,既然是包含多個命令對象,得有方法讓這多個命令對象能被組合進來

  • 第四,既然宏命令包含了多個命令對象,執行宏命令對象就相當於依次執行這些命令對象,也就是循環執行這些命令對象

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

/** 
 * 菜單對象,是個宏命令對象 
 */  
public class MenuCommand implements Command {  
    /** 
     * 用來記錄組合本菜單的多道菜品,也就是多個命令對象 
     */  
    private Collection<Command> col = new ArrayList<Command>();  
    /** 
     * 點菜,把菜品加入到菜單中 
     * @param cmd 客戶點的菜 
     */  
    public void addCommand(Command cmd){  
        col.add(cmd);  
    }  
    public void execute() {  
        //執行菜單其實就是循環執行菜單裏面的每個菜  
        for(Command cmd : col){  
            cmd.execute();  
        }  
    }  
}

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

/** 
 * 服務員,負責組合菜單,負責組裝每個菜和具體的實現者, 
 * 還負責執行調用,相當於標準Command模式的Client+Invoker 
 */  
public class Waiter {  
    /** 
     * 持有一個宏命令對象——菜單 
     */  
    private MenuCommand menuCommand = new MenuCommand();  
    /** 
     * 客戶點菜 
     * @param cmd 客戶點的菜,每道菜是一個命令對象 
     */  
    public void orderDish(Command cmd){  
        //客戶傳過來的命令對象是沒有和接收者組裝的  
        //在這裏組裝吧  
        CookApi hotCook = new HotCook();  
        CookApi coolCook = new CoolCook();  
        //判讀到底是組合涼菜師傅還是熱菜師傅  
        //簡單點根據命令的原始對象的類型來判斷  
        if(cmd instanceof DuckCommand){  
            ((DuckCommand)cmd).setCookApi(hotCook);  
        }else if(cmd instanceof ChopCommand){  
            ((ChopCommand)cmd).setCookApi(hotCook);  
        }else if(cmd instanceof PorkCommand){  
            //這是個涼菜,所以要組合涼菜的師傅  
            ((PorkCommand)cmd).setCookApi(coolCook);  
        }  
        //添加到菜單中  
        menuCommand.addCommand(cmd);  
    }  
    /** 
     * 客戶點菜完畢,表示要執行命令了,這裏就是執行菜單這個組合命令 
     */  
    public void orderOver(){  
        this.menuCommand.execute();  
    }  
}

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

public class Client {  
    public static void main(String[] args) {  
        //客戶只是負責向服務員點菜就好了  
        //創建服務員  
        Waiter waiter = new Waiter();  
          
        //創建命令對象,就是要點的菜  
        Command chop = new ChopCommand();  
        Command duck = new DuckCommand();  
        Command pork = new PorkCommand();  
          
        //點菜,就是把這些菜讓服務員記錄下來  
        waiter.orderDish(chop);  
        waiter.orderDish(duck);  
        waiter.orderDish(pork);  
          
        //點菜完畢  
        waiter.orderOver();  
    }  
}

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

本廚師正在做:綠豆排骨煲  
本廚師正在做:北京烤鴨  
涼菜蒜泥白肉已經做好,本廚師正在裝盤。

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

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

本文出自 “ciyo技術分享” 博客,請務必保留此出處http://ciyorecord.blog.51cto.com/6010867/1945510

命令模式(二)