在開發過程中,我可能會需要向某些對象發送一些請求,但是我們不知請求的具體接收者是誰,也不知道被請求的操作是那個,我們只知道在程序運行中指定具體的請求接收者即可。打個比方,電視遙控器,我們只需要知道按那個按鈕能夠打開電視、關閉電視和換台即可,並不需要知道是怎么開電視、關電視和換台的。對於這種情況,我們可以采用命令模式來進行設計。
一、基本定義
命令模式將請求封裝成對象,以便使用不同的請求、隊列或者日志來參數化其他對象。命令模式支持可撤銷的操作。
命令模式可以對發送者額接受者完全解耦,發送者也接收者之間並沒有直接的聯系,發送者只需要知道如何發送請求,不需要關心請求是如何完成了。這就是命令模式,命令模式將方法調用給封裝起來了。
二、模式結構
從上圖可以看出命令模式包含如下幾個角色:
Command: 抽象命令類
ConcreteCommand: 具體命令類
Invoker: 調用者
Receiver: 接收者
Client:客戶類
命令模式的本質就在於將命令進行封裝,將發出命令的責任和執行命令的責任分開,是的發送者只需要知道如何發送命令即可,不需要命令是如何實現的,甚至命令執行是否成功都不需要理會。同時命令模式使得請求也變成了一個對象,它像其他對象一樣可以被存儲和傳遞。
三、模式實現
這里以電視機為例。電視劇是請求的接受者,遙控器是請求的發送者,遙控器上有一些按鈕,不同的按鈕對應着不同的操作。在這里遙控器需要執行三個命令:打開電視機、關閉電視機、換台。
UML圖:
代碼的實現
抽象命令類:Command.java
1 /** 2 * Command命令接口,為所有的命令聲明一個接口。所有的命令都應該實現它 3 */ 4 public interface Command { 5 public void execute(); 6 }
電視機類:Television.java
1 public class Television { 2 public void open(){ 3 System.out.println("打開電視機......"); 4 } 5 6 public void close(){ 7 System.out.println("關閉電視機......"); 8 } 9 10 public void changeChannel(){ 11 12 System.out.println("切換電視頻道......"); 13 } 14 }
遙控器類:Controller.java
1 public class Controller { 2 private Command openTVCommand; 3 private Command closeTVCommand; 4 private Command changeChannelCommand; 5 6 public Controller(Command openTvCommand,Command closeTvCommand,Command changeChannelCommand){ 7 this.openTVCommand = openTvCommand; 8 this.closeTVCommand = closeTvCommand; 9 this.changeChannelCommand = changeChannelCommand; 10 } 11 12 /** 13 * 打開電視劇 14 */ 15 public void open(){ 16 openTVCommand.execute(); 17 } 18 19 /** 20 * 關閉電視機 21 */ 22 public void close(){ 23 closeTVCommand.execute(); 24 } 25 26 /** 27 * 換頻道 28 */ 29 public void change(){ 30 31 changeChannelCommand.execute(); 32 } 33 34 }
遙控器的三個按鈕
1 public class OpenTvCommand implements Command{ 2 private Television tv; 3 4 public OpenTvCommand(){ 5 tv = new Television(); 6 } 7 8 public void execute() { 9 tv.open(); 10 } 11 12 }
1 public class ChangeChannelCommand implements Command{ 2 private Television tv; 3 4 public ChangeChannelCommand(){ 5 tv = new Television(); 6 } 7 8 public void execute() { 9 tv.changeChannel(); 10 } 11 12 }
1 public class CloseTvCommand implements Command{ 2 private Television tv; 3 4 public CloseTvCommand(){ 5 tv = new Television(); 6 } 7 8 public void execute() { 9 tv.close(); 10 } 11 12 }
客戶端:Client.java
1 public class Client { 2 public static void main(String a[]) 3 { 4 Command openCommand,closeCommand,changeCommand; 5 6 openCommand = new OpenTvCommand(); 7 closeCommand = new CloseTvCommand(); 8 changeCommand = new ChangeChannelCommand(); 9 10 Controller control = new Controller(openCommand,closeCommand,changeCommand); 11 12 control.open(); //打開電視機 13 control.change(); //換頻道 14 control.close(); //關閉電視機 15 } 16 17 }
運行結果
四、模式優缺點
優點
1. 降低了系統耦合度
2. 新的命令可以很容易添加到系統中去。
缺點
使用命令模式可能會導致某些系統有過多的具體命令類。
五、模式使用場景
1.系統需要將請求調用者和請求接收者解耦,使得調用者和接收者不直接交互。
2.系統需要在不同的時間指定請求、將請求排隊和執行請求。
3.系統需要支持命令的撤銷(Undo)操作和恢復(Redo)操作。
5.系統需要將一組操作組合在一起,即支持宏命令。
六、模式擴展
1. 撤銷命令
在電視遙控器中,我們還有這樣一個按鈕,那就是返回。用於切換到上面一個頻道中去。在命令模式中也支持撤銷操作,在這里我們只需要記錄上一個頻道,然后將上一個頻道傳入即可。
在這里將Command進行一個簡單的修改:將execute()改為execute(int I );i表示頻道,用於進行頻道切換。
1 /** 2 * Command命令接口,為所有的命令聲明一個接口。所有的命令都應該實現它 3 */ 4 public interface Command { 5 /** 6 * 為了方便切換頻道,這里使用參數i將頻道傳遞 7 * @param i 8 */ 9 public void execute(int i); 10 }
然后在Controller中添加channelUndo()方法,用於進行頻道返回。並且需要進行一些簡單的修改。
1 public class Controller { 2 private Command openTVCommand; 3 private Command closeTVCommand; 4 private Command changeChannelCommand; 5 6 public int nowChannel = 0; //當前頻道 7 public int priorChannel; //前一個頻道,用於執行返回操作 8 9 public Controller(Command openTvCommand,Command closeTvCommand,Command changeChannelCommand){ 10 this.openTVCommand = openTvCommand; 11 this.closeTVCommand = closeTvCommand; 12 this.changeChannelCommand = changeChannelCommand; 13 } 14 15 /** 16 * 打開電視劇 17 */ 18 public void open(){ 19 openTVCommand.execute(0); 20 } 21 22 /** 23 * 關閉電視機 24 */ 25 public void close(){ 26 closeTVCommand.execute(0); 27 } 28 29 /** 30 * 換頻道:只在當前頻道遞增 31 */ 32 public void change(){ 33 priorChannel = nowChannel; //換頻道前記錄當前頻道 34 nowChannel++; //頻道+1 35 changeChannelCommand.execute(nowChannel); 36 } 37 38 /** 39 * 頻道返回 40 */ 41 public void ChannelUndo(){ 42 changeChannelCommand.execute(priorChannel); //將以前的頻道傳入 43 //當前頻道與前一個頻道進行互換 44 int tempChannel; 45 tempChannel = priorChannel; 46 priorChannel = nowChannel; 47 nowChannel = tempChannel; 48 } 49 }
客戶端
1 public class Client { 2 public static void main(String a[]) 3 { 4 Command openCommand,closeCommand,changeCommand; 5 6 openCommand = new OpenTvCommand(); 7 closeCommand = new CloseTvCommand(); 8 changeCommand = new ChangeChannelCommand(); 9 10 Controller control = new Controller(openCommand,closeCommand,changeCommand); 11 12 control.open(); //打開電視機 13 control.change(); //換頻道 14 control.change(); 15 control.ChannelUndo(); 16 control.ChannelUndo(); 17 control.ChannelUndo(); 18 control.close(); //關閉電視機 19 } 20 21 }
運行結果。
七、總結
1. 命令模式的本質就是將命令對象進行封裝打包,將發出命令的責任和執行命令的責任進行割開。
2. 命令模式中發送者只需要知道如何發送請求命令,無須關心命令執行具體過程。
3. 在發送者和接收者兩者間是通過命令對象進行溝通的。請求命令本身就當做一個對象在兩者間進行傳遞,它封裝了接收者和一組動作。
4. 命令模式支持撤銷。
5. 命令模式隊列請求和日志請求。