跳到主要內容
黯羽輕揚每天積累一點點

設計模式之命令模式(Command Pattern)

免費2015-03-06#Design_Pattern#命令模式#Command Pattern

命令模式,用來解耦請求者與執行者的一種設計模式,其擴展應用主要有隊列請求(把運算限制在指定的工作執行緒中)和日誌請求(用於生成日誌及恢復事務)。

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. 調用者(櫃台服務員)可以控制具體執行時機,但對具體執行者(廚師)的細節完全不清楚
  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.日誌請求

多用於資料庫管理系統的實現,我們需要把一系列的操作記錄下來(如寫在硬碟上),在遇到系統故障時讀出來以恢復數據,如何實現?利用物件的序列化把物件保存起來(記錄日誌),在需要的時候反序列化(恢復事務)

五.總結

命令模式可以有效地解耦請求者與執行者,還可以提供一些額外的好處(比如支援撤銷操作、隊列請求、記錄日誌等等)

評論

暫無評論,快來發表你的看法吧

提交評論