1. 程式人生 > 實用技巧 >設計模式—— 十五 :命令模式

設計模式—— 十五 :命令模式


@目錄

什麼是命令模式?

命令模式的定義:

Encapsulate a request as an object,thereby letting you parameterize clients with different requests,queue or log requests,and support undoable operations.(將一個請求封裝成一個物件,從而讓你使用不同的請求把客戶端引數化,對請求排隊或者記錄請求日誌,可以提供命令的撤銷和恢復功能。)

命令模式的核心在於引入了命令類,通過命令類來降低傳送者和接收者的耦合度,請求傳送 者只需指定一個命令物件,再通過命令物件來呼叫請求接收者的處理方法。

命令模式通用類圖如下:

圖15-1:命令模式模式通用類圖

命令模式包含這麼幾個角色:

  • Command(抽象命令):宣告需要執行的命令
  • ConcreteCommand(具體命令):實現宣告的命令
  • Receive(接收者):接收者執行與請求相關的操作,它具體實現對請求的業務處理
  • Invoker(呼叫者):呼叫者即請求傳送者,它通過命令物件來執行請求。

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

命令模式通用程式碼如下:

  • Receiver:Receiver類實現具體的業務需求
/**
 * @author 三分惡
 * @date 2020年6月28日
 * @description 接收者
 */
public class Receiver {
	public void doSomething() {
		//具體的業務邏輯
	}
}
  • 抽象的Command類:
/**
 * @author 三分惡
 * @date 2020年6月28日
 * @description 抽象的Command類
 */
public abstract class Command {
	// 每個命令類都必須有一個執行命令的方法
	public abstract void execute();
}

  • 具體的Command類:根據需求,具體的命令類可以有多個:
/**
 * @author 三分惡
 * @date 2020年6月28日
 * @description 具體的命令類
 */
public class ConcreteCommand extends Command {
	// 維持一個對請求接收者物件的引用
	private Receiver receiver; 

	//建構函式傳遞接收者
	public ConcreteCommand(Receiver receiver) {
		this.receiver = receiver;
	}

	public void execute() {
		 // 呼叫請求接收者的業務處理方法doSomething()
		receiver.doSomething();
	}
}
  • 呼叫者Invoker類:
/**
 * @author 三分惡
 * @date 2020年6月28日
 * @description 呼叫者
 */
public class Invoker {
	private Command command;
	
	// 設值注入
	public void setCommand(Command command) {
		this.command = command;
	}

	// 執行命令
	public void action() {
		this.command.execute();
	}
}
  • 場景類:
/**
 * @author 三分惡
 * @date 2020年6月28日 
 * @description  
 */
public class Client {

	public static void main(String[] args) {
		//首先宣告呼叫者Invoker 
		Invoker invoker = new Invoker(); 
		//定義接收者 
		Receiver receiver = new Receiver(); 
		//定義一個傳送給接收者的命令 
		Command command = new ConcreteCommand(receiver); 
		//把命令交給呼叫者去執行 
		invoker.setCommand(command); 
		invoker.action();
	}

}

為什麼要用命令模式?

假設有一個這樣的業務場景:一個正在開發的軟體專案,分為三個組:需求組、美工組、開發組。在開發的過程中,客戶需要和廠商溝通,和需求組討論需求、和 美工討論頁面、和程式碼組討論實現,告訴他們修改、刪除、增加各種內容等。

使用命令模式前

上面的業務場景抽象成類圖:

圖15-2:甲方要求業務場景初始類圖

根據類圖進行具體的編碼實現:

  • Group:抽象類,定義業務:
/**
 * @author 三分惡
 * @date 2020年6月28日
 * @description 抽象Group
 */
public abstract class Group {
	// 甲乙雙方分開辦公,如果甲方要和某個組討論,首先要找到這個組
	public abstract void find();

	// 要求增加功能
	public abstract void add();

	// 要求刪除功能
	public abstract void delete();

	// 要求修改功能
	public abstract void change();

	// 要求給出所有的變更計劃
	public abstract void plan();
}

  • RequirementGroup(需求組):
public class RequirementGroup extends Group{

	@Override
	public void find() {
		System.out.println("找到需求組...");
	}

	@Override
	public void add() {
		System.out.println("客戶要求增加一項需求...");
	}

	@Override
	public void delete() {
		System.out.println("客戶要求刪除一項需求...");
	}

	@Override
	public void change() {
		System.out.println("客戶要求修改一項需求...");
	}

	@Override
	public void plan() {
		System.out.println("客戶要求需求變更計劃...");
	}

}
  • PageGroup(美工組):
public class PageGroup extends Group{

	@Override
	public void find() {
		System.out.println("找到美工組...");
	}

	@Override
	public void add() {
		System.out.println("客戶要求增加一個頁面...");
	}

	@Override
	public void delete() {
		System.out.println("客戶要求刪除一個頁面...");
	}

	@Override
	public void change() {
		System.out.println("客戶要求改變一個頁面...");
	}

	@Override
	public void plan() {
		System.out.println("客戶要求頁面變更計劃...");
	}

}
  • CodeGroup(開發組):
public class CodeGroup extends Group{

	@Override
	public void find() {
		System.out.println("找到程式碼組...");
	}

	@Override
	public void add() {
		System.out.println("客戶要求增加一項功能...");
	}

	@Override
	public void delete() {
		System.out.println("客戶要求刪除一項功能...");
	}

	@Override
	public void change() {
		System.out.println("客戶要求修改一項功能...");
	}

	@Override
	public void plan() {
		System.out.println("客戶要求程式碼變更計劃...");
	}

}
  • 場景類:
public class Client {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		//首先客戶找到需求組說,過來談需求,並修改
		System.out.println("-----------客戶要求增加一項需求---------------"); 
		Group rg = new RequirementGroup();
		//找到需求組
		rg.find(); 
		//增加一個需求 
		rg.add(); 
		//要求變更計劃 
		rg.plan();
	}

}

執行結果:

場景類中客戶找到了需求組,向需求組提出了增加需求和需求變更的要求。

這樣存在什麼問題呢?甲方對乙方做一些要求的時候,要找到對應的組,但是不能希望甲方是非常專業的,可能提需求提到了開發組頭上,要求頁面變更提到了需求組頭上。


使用命令模式後

這是為什麼專案需要專案經理的原因之一。

甲方不管你什麼需求、美工、開發,只管把專案經理叫過去,告訴專案經理他們的要求。專案經理再根據甲方的要求向各個組下達命令。對應的,就可以引入命令模式。

圖15-3:甲方要求業務場景引入命令模式的類圖

來看看具體的程式碼:

  • 抽象命令類:
public abstract  class Command {
	//把三個組都定義好,子類可以直接使用 
	protected RequirementGroup rg = new RequirementGroup();   //需求組
	protected PageGroup pg = new PageGroup(); //美工組 
	protected CodeGroup cg = new CodeGroup(); //程式碼組 
	//只有一個方法,你要我做什麼事情 
	public abstract void execute();
}
  • 增加需求的命令:
public class AddRequirementCommand extends Command{

	@Override
	public void execute() {
		//找到需求組 
		super.rg.find(); 
		//增加一份需求
		super.rg.add(); 
		//給出計劃 
		super.rg.plan();
	}

}
  • 刪除頁面的命令:
public class DeletePageCommand extends Command{

	@Override
	public void execute() {
		//找到頁面組 
		super.pg.find(); 
		//刪除一個頁面 
		super.rg.delete();
		//給出計劃 
		super.rg.plan();
	}

}
  • 負責人Invoker:負責人只要接到客戶的命令,就立刻執行。
public class Invoker {
	// 什麼命令
	private Command command;

	// 客戶發出命令
	public void setCommand(Command command) {
		this.command = command;
	}

	// 執行客戶的命令
	public void action() {
		this.command.execute();
	}
}
  • 場景類:模擬客戶增加需求的命令。
public class Client {
	public static void main(String[] args) {
		// 定義呼叫者
		Invoker jingli = new Invoker();
		// 客戶要求增加一項需求
		System.out.println("------------客戶要求增加一項需求---------------");
		// 客戶下命令
		Command command = new AddRequirementCommand();
		// 呼叫者接收到命令
		jingli.setCommand(command);
		// 呼叫者執行命令
		jingli.action();
	}
}

場景類就簡單了很多,如果客戶提出其它要求,如刪除頁面,只需要:

// 客戶下命令
//Command command = new AddRequirementCommand();
Command command = new DeletePageCommand();

同樣比較簡單。

這樣一來,客戶提要求的時候,不需要知道具體誰去完成這個要求,只需求把這個要求提給專案經理即可。

高內聚的要求就被滿足了。


命令模式優缺點

優點

● 類間解耦
呼叫者角色與接收者角色之間沒有任何依賴關係,呼叫者實現功能時只需呼叫Command 抽象類的execute方法就可以,不需要了解到底是哪個接收者執行。

● 可擴充套件性 Command的子類可以非常容易地擴充套件,而呼叫者Invoker和高層次的模組Client不產生嚴 重的程式碼耦合。

缺點

使用命令模式可能會導致某些系統有過多的具體命令類。因為針對每一個對請求接收者的調 用操作都需要設計一個具體命令類,因此在某些系統中可能需要提供大量的具體命令類,這 將影響命令模式的使用。


命令模式使用場景

在以下情況下可以考慮使用命令模式:

  • 系統需要將請求呼叫者和請求接收者解耦,使得呼叫者和接收者不直接互動。請求呼叫者 無須知道接收者的存在,也無須知道接收者是誰,接收者也無須關心何時被呼叫。
  • 系統需要在不同的時間指定請求、將請求排隊和執行請求。一個命令物件和請求的初始調 用者可以有不同的生命期,換言之,最初的請求發出者可能已經不在了,而命令物件本身仍 然是活動的,可以通過該命令物件去呼叫請求接收者,而無須關心請求呼叫者的存在性,可 以通過請求日誌檔案等機制來具體實現。
  • 系統需要支援命令的撤銷(Undo)操作和恢復(Redo)操作。




參考:

【1】:《Java設計模式之禪》
【2】:《design-pattern-java》
【3】:《研磨設計模式》