no_mkd
一.什麼是命令模式?
命令模式,封裝了方法調用細節,以解耦請求者與執行者,具體流程如下:
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.宏命令(多條命令順序執行)
我們可以定義「命令的命令」來實現(這種特殊的命令的 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.日誌請求
多用於資料庫管理系統的實現,我們需要把一系列的操作記錄下來(如寫在硬碟上),在遇到系統故障時讀出來以恢復數據,如何實現?利用物件的序列化把物件保存起來(記錄日誌),在需要的時候反序列化(恢復事務)
五.總結
命令模式可以有效地解耦請求者與執行者,還可以提供一些額外的好處(比如支援撤銷操作、隊列請求、記錄日誌等等)
暫無評論,快來發表你的看法吧