1. 程式人生 > >JAVA設計模式--命令模式

JAVA設計模式--命令模式

一、什麼是命令式

命令(Command)模式又叫作動作(Action)模式或事務(Transaction)模式,是一種物件的行為模式。將一個請求封裝為一個物件,從而使你可用不同的請求對客戶進行引數化;對請求排隊或記錄請求日誌,以及支援可撤消的操作。
命令模式的本質:封裝請求
設計意圖:
命令模式通過將請求封裝到一個命令(Command)物件中,實現了請求呼叫者和具體實現者之間的解耦。

二、命令模式的適用性

在以下條件下可以考慮使用命令模式:
• 如果需要抽象出需要執行的動作,並引數化這些物件,可以選用命令模式。將這些需要執行的動作抽象成為命令,然後實現命令的引數化配置。
• 如果需要在不同的時刻指定、排列和執行請求,可以選用命令模式。將這些請求封裝成為命令物件,然後實現將請求佇列化。
• 如果需要支援取消操作,可以選用命令模式,通過管理命令物件,能很容易地實現命令的恢復和重做功能。
• 如果需要支援當系統崩潰時,能將系統的操作功能重新執行一遍,可以選用命令模式。將這些操作功能的請求封裝成命令物件,然後實現日誌命令,就可以在系統恢復以後,通過日誌獲取命令列表,從而重新執行一遍功能。
• 在需要事務的系統中,可以選用命令模式。命令模式提供了對事務進行建模的方法。命令模式有一個別名就是Transaction。

三、命令模式的結構


命令模式涉及的角色及其職責如下:

抽象命令(Command)角色:一般定義為介面,用來定義執行命令的介面。
具體命令(ConcreteCommand)角色:通常會持有接收者物件,並呼叫接收者物件的相應功能來完成命令要執行的操作。
接收者(Receiver)角色:真正執行命令的物件。任何類都可能成為接收者,只要它能夠實現命令要求實現的相應功能。
呼叫者(Invoker)角色:要求命令物件執行請求,通常會持有命令物件,可以持有很多的命令物件。這個是客戶端真正觸發命令並要求命令執行相應操作的地方,也就是說相當於使用命令物件的入口。
客戶端(Client)角色:建立具體的命令物件,並且設定命令物件的接收者。
命令模式結構示意原始碼如下:

先來看看抽象命令介面的定義。示例程式碼如下:

/**
 * 命令介面
 */
public interface Command {

	/**
	 * 執行命令
	 */
	public void execute();
}

接下來看看具體命令是如何實現的。示例程式碼如下:

/**
 * 具體的命令實現
 */
public class ConcreteCommand implements Command {

	/**
	 * 持有相應的接收者物件
	 */
	private Receiver receiver = null;

	/**
	 * 構造方法,傳入相應的接收者物件
	 * 
	 * @param receiver 相應的接收者物件
	 */
	public ConcreteCommand(Receiver receiver) {
		this.receiver = receiver;
	}

	/**
	 * 執行命令
	 */
	@Override
	public void execute() {
		// 通常會轉調接收者物件的相應方法,讓接收者來真正執行功能
		receiver.action();
	}

}

再來看看接收者的定義。示例程式碼如下:

/**
 * 命令的接收者
 */
public class Receiver {

	/**
	 * 示意方法,真正執行命令相應的操作
	 */
	public void action() {
		System.out.println("接收者開始行動。。。");
	}
}

下面該來看看呼叫者如何實現的了。示例程式碼如下:

/**
 * 命令的呼叫者
 */
public class Invoker {

	/**
	 * 持有命令物件
	 */
	private Command command = null;

	/**
	 * 設定呼叫者持有的命令物件
	 * 
	 * @param command 命令物件
	 */
	public void setCommand(Command command) {
		this.command = command;
	}

	/**
	 * 示意方法,呼叫命令執行請求
	 */
	public void invoke() {
		command.execute();
	}
}

再來看看客戶端的實現。

public class Client {
	public static void main(String[] args) {
		// 建立接收者
		Receiver receiver = new Receiver();
		// 建立命令物件,設定它的接收者
		Command command = new ConcreteCommand(receiver);
		// 建立呼叫者,把命令物件設定進去
		Invoker invoker = new Invoker();
		invoker.setCommand(command);
		// 呼叫者呼叫命令
		invoker.invoke();
	}
}

四、命令模式的優點

更鬆散的耦合
    命令模式使得發起命令的物件——客戶端,和具體實現命令的物件——接收者物件完全解耦,也就是說發起命令的物件完全不知道具體實現物件是誰,也不知道如何實現。
更動態的控制
    命令模式把請求封裝起來,可以動態地對它進行引數化、佇列化和日誌化等操作,從而使得系統更靈活。
很自然的複合命令
    命令模式中的命令物件能夠很容易地組合成複合命令,如巨集命令,從而使系統操作更簡單,功能更強大。
更好的擴充套件性
    由於發起命令的物件和具體的實現完全解耦,因此擴充套件新的命令就很容易,只需要實現新的命令物件,然後在裝配的時候,把具體的實現物件設定到命令物件中,然後就可以使用這個命令物件,已有的實現完全不用變化。

五、認識命令模式

(1)引數化配置
    所謂命令模式的引數化配置,指的是:可以用不同的命令物件,去引數化配置客戶的請求。即:對於Invoker的同一個請求,為其配置不同的Command物件,就會執行不同的功能。
(2)可撤銷的操作
    可撤銷操作的意思就是:放棄該操作,回到未執行該操作前的狀態。
    有兩種基本的思路來實現可撤銷的操作,一種是補償式,又稱反操作式,比如要撤銷的操作是加的功能,那麼可以用相反的操作即減的功能去實現,撤銷加多少就減多少。
    使用命令模式可以實現補償式的可撤銷操作,首先需要把操作過的命令記錄下來,形成命令的歷史列表,撤銷的時候就從最後一個開始執行撤銷。同樣的方式,還可以實現恢復的功能。
    另外一種實現可撤銷操作的方式是儲存恢復式,意思就是把操作前的狀態記錄下來,然後要撤銷操作的時候就直接恢復回去就可以了(可以使用備忘錄模式實現)。
(3)巨集命令
    什麼是巨集命令呢?簡單點說就是包含多個命令的命令,是一個命令的組合。以去飯店吃飯為例,一般的流程都是:選座位-->點菜-->上菜-->享用美食-->結賬,如果將這幾個步驟中涉及的命令打包一起執行的話,就可以看作是一個巨集命令。
(4)佇列請求
    所謂佇列請求,就是對命令物件進行排隊,組成命令佇列,然後依次取出命令物件來執行。還是以去飯店吃飯為例,已經點菜的顧客可能有很多人,點的每一道菜都可以看成是一條命令(需要廚師去做菜,服務員上菜),這些被點的菜就構成了一個命令佇列。廚師一般都會按照點菜的先後順序去做菜,誰的菜先點,就先做誰的。
(5)日誌請求
    所謂日誌請求,就是把請求的歷史紀錄儲存下來,一般是採用永久儲存的方式。如果在執行請求的過程中,系統崩潰了,那麼當系統再次執行時,就可以從儲存的歷史記錄中獲取日誌請求,並重新執行命令。
    日誌請求的實現有兩種方案:一種是直接使用Java中的序列化方法;另外一種就是在命令物件中新增上儲存和裝載的方法,其實就是讓命令物件自己實現類似序列化的功能。

六、總結

    命令模式是對命令的封裝。命令模式把發出命令的責任和執行命令的責任分割開,委派給不同的物件。
    每一個命令都是一個操作:請求的一方發出請求要求執行一個操作;接收的一方收到請求,並執行操作。命令模式允許請求的一方和接收的一方獨立開來,使得請求的一方不必知道接收請求的一方的介面,更不必知道請求是怎麼被接收,以及操作是否被執行、何時被執行,以及是怎麼被執行的。