命令模式-Command

1. 智能生活項目需求

看一個具體的需求

在這裏插入圖片描述

  1. 我們買了一套智能家電,有照明燈、風扇、冰箱、洗衣機,我們只要在手機上安裝 app 就可以控制對這些家電工作。
  2. 這些智能家電來自不同的廠家,我們不想針對每一種家電都安裝一個 App,分別控制,我們希望只要一個 app就可以控制全部智能家電。
  3. 要實現一個 app 控制所有智能家電的需要,則每個智能家電廠家都要提供一個統一的接口給 app 調用,這時 就可以考慮使用命令模式。
  4. 命令模式可將“動作的請求者”從“動作的執行者”對象中解耦出來.
  5. 在我們的例子中,動作的請求者是手機 app,動作的執行者是每個廠商的一個家電產品

2. 命令模式基本介紹

  1. 命令模式(Command Pattern):在軟件設計中,我們經常需要向某些對象發送請求,但是並不知道請求的接收者是誰,也不知道被請求的操作是哪個,我們只需在程序運行時指定具體的請求接收者即可,此時,可以使用命令模式來進行設計

  2. 命名模式使得 請求發送者與請求接收者消除彼此之間的耦合,讓對象之間的調用關係更加靈活,實現解耦。

  3. 在命名模式中,會將一個請求封裝爲一個對象,以便使用不同參數來表示不同的請求(即命名),同時命令模式也支持可撤銷的操作。

  4. 通俗易懂的理解:將軍發佈命令,士兵去執行。其中有幾個角色:將軍(命令發佈者)、士兵(命令的具體執行者)、命令(連接將軍和士兵)。Invoker 是調用者(將軍),Receiver 是被調用者(士兵),MyCommand 是命令,實現了 Command 接口,持有接收對象。

3.命令模式的原理類圖

在這裏插入圖片描述

  • 對原理類圖的說明-即(命名模式的角色及職責)
    1. Invoker 是調用者角色
    2. Command: 是命令角色,需要執行的所有命令都在這裏,可以是接口或抽象類
    3. Receiver: 接受者角色,知道如何實施和執行一個請求相關的操作
    4. **ConcreteCommand: **將一個接受者對象與一個動作綁定,調用接受者相應的操作,實現 execute

4. 命令模式解決智能生活項目

應用實例要求

  1. 編寫程序,使用命令模式 完成前面的智能家電項目
  2. 思路分析和圖解
    在這裏插入圖片描述
    3) 代碼如下
/**
 * 創建命令接口
 * @author Administrator
 *
 */
public interface Command {
	//執行動作(操作)
	public void execute();
	//撤銷動作(操作)
	public void undo();
}

public class LightOffCommand implements Command {

	// 聚合LightReceiver

	LightReceiver light;

	// 構造器
	public LightOffCommand(LightReceiver light) {
			super();
			this.light = light;
		}

	@Override
	public void execute() {
		// TODO Auto-generated method stub
		// 調用接收者的方法
		light.off();
	}

	@Override
	public void undo() {
		// TODO Auto-generated method stub
		// 調用接收者的方法
		light.on();
	}
}

public class LightOnCommand implements Command {

	//聚合LightReceiver
	
	LightReceiver light;
	
	//構造器
	public LightOnCommand(LightReceiver light) {
		super();
		this.light = light;
	}
	
	@Override
	public void execute() {
		// TODO Auto-generated method stub
		//調用接收者的方法
		light.on();
	}

	

	@Override
	public void undo() {
		// TODO Auto-generated method stub
		//調用接收者的方法
		light.off();
	}

}
public class LightReceiver {
	public void on() {
		System.out.println(" 電燈打開了.. ");
	}
	
	public void off() {
		System.out.println(" 電燈關閉了.. ");
	}
}
public class TVOffCommand implements Command {

	// 聚合TVReceiver

	TVReceiver tv;

	// 構造器
	public TVOffCommand(TVReceiver tv) {
		super();
		this.tv = tv;
	}

	@Override
	public void execute() {
		// TODO Auto-generated method stub
		// 調用接收者的方法
		tv.off();
	}

	@Override
	public void undo() {
		// TODO Auto-generated method stub
		// 調用接收者的方法
		tv.on();
	}
}

public class TVOnCommand implements Command {

	// 聚合TVReceiver

	TVReceiver tv;

	// 構造器
	public TVOnCommand(TVReceiver tv) {
		super();
		this.tv = tv;
	}

	@Override
	public void execute() {
		// TODO Auto-generated method stub
		// 調用接收者的方法
		tv.on();
	}

	@Override
	public void undo() {
		// TODO Auto-generated method stub
		// 調用接收者的方法
		tv.off();
	}
}

public class TVReceiver {
	public void on() {
		System.out.println(" 電視機打開了.. ");
	}
	
	public void off() {
		System.out.println(" 電視機關閉了.. ");
	}
}
/**
 * 沒有任何命令,即空執行: 用於初始化每個按鈕, 當調用空命令時,對象什麼都不做
 * 其實,這樣是一種設計模式, 可以省掉對空判斷
 * @author Administrator
 *
 */
public class NoCommand implements Command {

	@Override
	public void execute() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void undo() {
		// TODO Auto-generated method stub
		
	}

}
public class RemoteController {

	// 開 按鈕的命令數組
	Command[] onCommands;
	Command[] offCommands;

	// 執行撤銷的命令
	Command undoCommand;

	// 構造器,完成對按鈕初始化

	public RemoteController() {

		onCommands = new Command[5];
		offCommands = new Command[5];

		for (int i = 0; i < 5; i++) {
			onCommands[i] = new NoCommand();
			offCommands[i] = new NoCommand();
		}
	}

	// 給我們的按鈕設置你需要的命令
	public void setCommand(int no, Command onCommand, Command offCommand) {
		onCommands[no] = onCommand;
		offCommands[no] = offCommand;
	}

	// 按下開按鈕
	public void onButtonWasPushed(int no) { // no 0
		// 找到你按下的開的按鈕, 並調用對應方法
		onCommands[no].execute();
		// 記錄這次的操作,用於撤銷
		undoCommand = onCommands[no];

	}

	// 按下開按鈕
	public void offButtonWasPushed(int no) { // no 0
		// 找到你按下的關的按鈕, 並調用對應方法
		offCommands[no].execute();
		// 記錄這次的操作,用於撤銷
		undoCommand = offCommands[no];

	}
	
	// 按下撤銷按鈕
	public void undoButtonWasPushed() {
		undoCommand.undo();
	}

}


public class Client {

	public static void main(String[] args) {
		//使用命令設計模式,完成通過遙控器,對電燈的操作
		//創建電燈的對象(接受者)
		LightReceiver lightReceiver = new LightReceiver();
		
		//創建電燈相關的開關命令
		LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);
		LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);
		
		//需要一個遙控器
		RemoteController remoteController = new RemoteController();
		
		//給我們的遙控器設置命令, 比如 no = 0 是電燈的開和關的操作
		remoteController.setCommand(0, lightOnCommand, lightOffCommand);
		
		System.out.println("--------按下燈的開按鈕-----------");
		remoteController.onButtonWasPushed(0);
		System.out.println("--------按下燈的關按鈕-----------");
		remoteController.offButtonWasPushed(0);
		System.out.println("--------按下撤銷按鈕-----------");
		remoteController.undoButtonWasPushed();
		
		
		System.out.println("=========使用遙控器操作電視機==========");
		
		TVReceiver tvReceiver = new TVReceiver();
		
		TVOffCommand tvOffCommand = new TVOffCommand(tvReceiver);
		TVOnCommand tvOnCommand = new TVOnCommand(tvReceiver);
		
		//給我們的遙控器設置命令, 比如 no = 1 是電視機的開和關的操作
		remoteController.setCommand(1, tvOnCommand, tvOffCommand);
		
		System.out.println("--------按下電視機的開按鈕-----------");
		remoteController.onButtonWasPushed(1);
		System.out.println("--------按下電視機的關按鈕-----------");
		remoteController.offButtonWasPushed(1);
		System.out.println("--------按下撤銷按鈕-----------");
		remoteController.undoButtonWasPushed();

	}

}

5. 實現宏命令

什麼是宏命令?簡單點說即使包含多個命令的命令,是一個命令的組合。舉個例子來說吧,設想一下你去飯店喫飯的過程。

  • 你走進一家飯店,找到座位坐下。
  • 服務員走過來,遞給你菜譜。
  • 你開始點菜,服務員開始記錄菜單,菜單是三聯的,點菜完畢,服務員就會把菜單分成三份,一份給後廚,一份給收銀臺,一份保留備查;
  • 點完菜,你坐在座位上等候,後廚會按照菜單做菜。
  • 每做好一份菜,就會由服務員送到你桌子上
  • 然後你就可以吃了

在這裏插入圖片描述

  • 菜單就相當於我們說的宏命令

如果實現宏命令

(1) 先定義接收者,就是廚師的接口和實現,先看接口。示例如下:

/**
 * 廚師的接口
 * @author Administrator
 *
 */
public interface CookApi {
	/**
	 * 做菜的方法
	 * @param name 菜名
	 */
	public void cook(String name);
}

廚師又分爲兩類,一類是做熱菜的師傅;一類是做涼菜的師傅,先看看做熱菜的廚師,示例代碼如下:

/**
 * 廚師對象,做熱菜
 * @author Administrator
 *
 */
public class HotCook implements CookApi{

	@Override
	public void cook(String name) {
		System.out.println("本廚師正在做: "+name);
	}
}


做涼菜的師傅如下:

/**
 * 廚師對象,做涼菜
 * @author Administrator
 *
 */
public class CoolCook implements CookApi{

	@Override
	public void cook(String name) {
		System.out.println("涼菜: "+name+"已經做好,本廚師正在裝盤!");
	}
}


接下來定義命令接口,示例如下:

/**
 * 命令接口,聲明執行的操作
 * @author Administrator
 *
 */
public interface Command {
	
	/**
	 * 執行命令對應的操作
	 */
	public void execute();

}


接下來具體定義具體命令,

/**
 * 命令對象,玉米排骨湯
 * @author Administrator
 *
 */
public class ChopCommand implements Command{
	
	/**
	 * 持有具體做菜的廚師的對象
	 */
	private CookApi cookApi;
	
	/**
	 * 設置具體做菜的廚師的對象
	 * @param cookApi 具體做菜的廚師的對象
	 */
	public void setCookApi(CookApi cookApi) {
		this.cookApi = cookApi;
	}

	@Override
	public void execute() {
		this.cookApi.cook("玉米排骨湯");
	}

}

/**
 * 命令對象,燉鴨子
 * @author Administrator
 *
 */
public class DuckCommand implements Command{
	
	/**
	 * 持有具體做菜的廚師的對象
	 */
	private CookApi cookApi;
	
	/**
	 * 設置具體做菜的廚師的對象
	 * @param cookApi 具體做菜的廚師的對象
	 */
	public void setCookApi(CookApi cookApi) {
		this.cookApi = cookApi;
	}

	@Override
	public void execute() {
		this.cookApi.cook("燉鴨子");
	}

}


/**
 * 命令對象,涼拌土豆絲
 * @author Administrator
 *
 */
public class TuDouCommand implements Command{
	
	/**
	 * 持有具體做菜的廚師的對象
	 */
	private CookApi cookApi;
	
	/**
	 * 設置具體做菜的廚師的對象
	 * @param cookApi 具體做菜的廚師的對象
	 */
	public void setCookApi(CookApi cookApi) {
		this.cookApi = cookApi;
	}

	@Override
	public void execute() {
		this.cookApi.cook("涼拌土豆絲");
	}
}


(4) 該來組合菜單對象了,也就是宏命令對象

public class MenuCommand implements Command{
	
	/**
	 * 用來記錄組合本菜單的多道菜品,也就是多個命令對象
	 */
	private Collection<Command> col = new ArrayList<Command>();
	
	/**
	 * 點菜,把菜品加入到菜單中
	 * @param cmd 客戶點的菜
	 */
	public void addCommand(Command cmd) {
		col.add(cmd);
	}

	@Override
	public void execute() {
		//執行菜單其實就是循環執行菜單裏面的每個菜
		for(Command cmd : col) {
			cmd.execute();
		}
		
	}

}

(5) 該服務員類登場了,它實現了功能,相當於標準命令模式實現中Client加上Invoker, 代碼實現如下:

/**
 * 服務員,負責組合菜單,負責組裝每個菜和具體的實現者
 * 還負責執行調用,相當於標準Command 模式的Client + Invoker
 * @author Administrator
 *
 */
public class Waiter {
	/**
	 * 持有一個宏命令對象 - 菜單
	 */
	private MenuCommand menuCommand = new MenuCommand();
	
	public  void orderDish(Command cmd) {
		// 客戶傳過來的命令對象時是沒有和接受者組裝的
		//在這裏組裝
		CookApi hotCook = new HotCook();
		CookApi coolCook = new CoolCook();
		if(cmd instanceof DuckCommand) {
			((DuckCommand)cmd).setCookApi(hotCook);
		}else if(cmd instanceof ChopCommand) {
			((ChopCommand)cmd).setCookApi(hotCook);
		}else if(cmd instanceof TuDouCommand) {
			((TuDouCommand)cmd).setCookApi(coolCook);
		}
		menuCommand.addCommand(cmd);
	}
	
	/**
	 * 客戶點菜完畢,表示要執行命令了,這裏就是執行菜單這個組合命令
	 */
	public void orderOver() {
		this.menuCommand.execute();
	}

}

(6) 測試類

public class Client {

	public static void main(String[] args) {
		// 客戶執行負責向服務員點菜就好了
		Waiter waiter = new Waiter();
		
		//創建命令對象,就是要點的菜
		Command chop = new ChopCommand();
		Command duck = new DuckCommand();
		Command tudou = new TuDouCommand();
		
		//點菜,就是把這些菜讓服務員記錄下來
		
		waiter.orderDish(chop);
		waiter.orderDish(duck);
		waiter.orderDish(tudou);
		
		//點菜完畢
		waiter.orderOver();
	}
}


6. 命令模式的注意事項和細節

  1. 將發起請求的對象與執行請求的對象解耦。發起請求的對象是調用者,調用者只要調用命令對象的 execute()方法就可以讓接收者工作,而不必知道具體的接收者對象是誰、是如何實現的,命令對象會負責讓接收者執行請求的動作,也就是說:”請求發起者”和“請求執行者”之間的解耦是通過命令對象實現的,命令對象起到了
    紐帶橋樑的作用。
  2. 容易設計一個命令隊列。只要把命令對象放到列隊,就可以多線程的執行命令
  3. 容易實現對請求的撤銷和重做
  4. 命令模式不足:可能導致某些系統有過多的具體命令類,增加了系統的複雜度,這點在在使用的時候要注意
  5. 空命令也是一種設計模式,它爲我們 省去了判空的操作。在上面的實例中,如果沒有用空命令,我們每按下一個按鍵都要判空,這給我們編碼帶來一定的麻煩。
  6. 命令模式經典的應用場景:界面的一個按鈕都是一條命令、模擬 CMD(DOS 命令)訂單的撤銷/恢復、觸發-反饋機制

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章