一.什么是命令模式?
命令模式,封裝了方法調用細節,以解耦請求者與執行者,具體流程如下:
1.從請求者(客戶)的角度看
請求者(客戶)發出請求 -> 調用者(系統)構造命令對象封裝請求 -> 調用者調用命令對象的指定方法(請求被執行)
很明顯,請求者根本不知道執行者是誰,更不知道具體執行細節。當然請求者本身也並不關心這些,它只要知道請求被執行了就好。
2.從執行者(低層組件)的角度看
執行者(低層組件)被調用 -> 執行者調用內部方法(請求被執行)
同樣的,執行者根本不知道請求者是誰,甚至不清楚調用者,不過沒關系,執行者只要本本分分的做好本職工作就好了,沒必要知道領導的情況。
3.從調用者(系統)的角度看
接到請求 -> 創建命令對象封裝請求 -> 在適當的時候調用命令對象的動作來執行請求(請求被執行)
調用者不知道執行者是誰,也不清楚請求者,它只負責構造命令並控制命令被執行,這就足夠了。
從上面可以看出各個對象之間的低耦合關系:
請求者(客戶)與執行者(低層組件)被徹底解耦,作為中間人的調用者也不了解請求者與執行者的具體細節,它們被很好的保護了起來
這正是我們想要的。
二.舉個例子
現實世界中任何一個稍微復雜的子系統都應當有一套命令,比如餐館的運行機制:
顧客A來到餐館點一碗面(發出請求) -> 櫃台服務員記錄下來(創建命令) -> 服務員把小票扔給廚房 -> 廚師C很快做好了一碗面(請求被執行)
顧客不知道將由誰來做這碗面,櫃台服務員也不知道,廚師不知道是誰點了這碗面,只知道做完面就可以休息了
是不是與命令模式很相像?
不妨用代碼來實現上面的機制
首先,我們需要一個命令接口,畢竟命令才是命令模式的核心,沒有命令,一切都是空想
package CommandPattern; /** * @author ayqy * 定義Command接口 */ public interface Command { public abstract void execute();//只需要定義一個統一的執行方法 }
有了命令還需要執行者,否則只有將軍沒有小兵,餐館的執行者當然是廚師:
package CommandPattern; /** * @author ayqy * 定義Chef基類 */ public abstract class Chef { //在此定義廚師的公共屬性 /** * 定義烹飪方法 */ public abstract void cook(); //在此定義其它有用的方法 }
我們還需要實現具體的廚師,術業有專攻:
做面的廚師:
package CommandPattern; /** * @author ayqy * 定義專業做面的廚師 */ public class NoodlesChef extends Chef{ @Override public void cook() { System.out.println("做好了一碗美味的拉面"); } }
做餅的廚師:
package CommandPattern; /** * @author ayqy * 定義專業做餅的廚師 */ public class PieChef extends Chef{ @Override public void cook() { System.out.println("做好了一塊香噴噴的大餅"); } }
有了小兵,有了將軍,我們還需要一套完整的命令:
package CommandPattern; /** * @author ayqy * 實現具體NoodlesCommand */ public class NoodlesCommand implements Command{ private NoodlesChef chef;//專業做面的廚師 public NoodlesCommand(){ chef = new NoodlesChef(); } @Override public void execute() { chef.cook(); //調用其它需要的方法 } }
package CommandPattern; /** * @author ayqy * 實現具體PieCommand */ public class PieCommand implements Command{ private PieChef chef;//專業做餅的廚師 public PieCommand(){ chef = new PieChef(); } @Override public void execute() { chef.cook(); //調用其它需要的方法 } }
准備工作做好了,餐館可以開張了
三.效果示例
需要一個Test類:
package CommandPattern; /** * @author ayqy * 實現測試類 */ public class Test { public static void main(String[] args) { System.out.println("Command Pattern餐館開張。。"); System.out.println("第一位客戶X先生"); System.out.println("X先生:你好,我需要一碗面,我餓極了"); NoodlesCommand nCmd = new NoodlesCommand(); System.out.println("櫃台服務員:好的,我已經記下了,馬上就好"); System.out.println("櫃台服務員:廚房~~,接單"); nCmd.execute(); System.out.println("X先生:真快啊!"); System.out.println(); System.out.println("第二位客戶XX先生"); System.out.println("XX先生:你好,我需要一塊餅,20分鍾后來取"); PieCommand pCmd = new PieCommand(); System.out.println("櫃台服務員:好的,我已經記下了"); System.out.println("15分鍾后"); System.out.println("櫃台服務員:廚房~~,接單"); pCmd.execute(); System.out.println("XX先生:真准時啊!"); } }
結果示例:
從例子可以看出:
1.調用者(櫃台服務員)可以控制具體執行時機,但對具體執行者(廚師)的細節完全不清楚
2.請求者(顧客)完全不知道餐館的運行機制,不知道點的餐是廚師做的還是服務員做的或者是從隔壁買的。。
3.執行者(廚師)完全不知道請求者的情況,它只做了本職工作,其它的什么都不知道
四.命令模式的擴展
1.宏命令(多條命令順序執行)
我們可以定義“命令的命令”來實現(這種特殊的命令的execute方法內部是順序調用其它若干命令的execute方法。。)
2.撤銷
假如來了很多顧客,點了很多份餐點,過了一會兒有幾個顧客等不及了需要撤銷,我們如何實現?
維護一個命令列表,記錄已經創建的命令,撤銷時需要找到對應的命令,執行撤銷操作
當然,前提是命令對象支持撤銷,我們需要做一些修改:
package CommandPattern; /** * @author ayqy * 定義Command接口 */ public interface Command { public abstract void execute();//只需要定義一個統一的執行方法 public abstract void undo();//定義統一的撤銷方法 }
各個命令的撤銷操作可能不同,因此定義為抽象方法,由子類來實現具體操作
*如何支持多步順序撤銷?
餐館的例子可能不需要這樣的功能,不妨想想另一個情景,文本編輯器
用戶發出了一系列命令,完成了一些列操作,后來發現並不需要這樣做,用戶會撤銷修改(Ctrl + Z),這時我們需要執行相反的操作對內容作以還原,要如何實現?
還是要先實現各個命令的undo行為(執行與execute相反順序的操作即可),除此之外,我們還需要一個棧來記錄已經被執行過的操作,以支持撤銷到初始狀態
3.隊列請求
我們可以建立一個工作線程,負責所有運算,想象有一個通道,輸入是一條條不同命令,輸出是命令的執行結果
可能上一刻工作線程在做大餅,下一刻已經出去買菜了。。
*這樣做有什么好處?
可以把運算限制在指定的幾個線程中,加以控制
4.日志請求
多用於數據庫管理系統的實現,我們需要把一系列的操作記錄下來(如寫在硬盤上),在遇到系統故障時讀出來以恢復數據,如何實現?
利用對象的序列化把對象保存起來(記錄日志),在需要的時候反序列化(恢復事務)
五.總結
命令模式可以有效地解耦請求者與執行者,還可以提供一些額外的好處(比如支持撤銷操作、隊列請求、記錄日志等等)