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