基本介紹
在軟體設計中,我們經常需要向某些物件傳送一些請求,但是並不知道請求的接收者是誰,也不知道被請求的操作是哪個,我們只需要在程式執行時指定具體的請求接收者即可,此時,可以使用命令模式來設計,使得請求傳送者與請求接收者消除彼此之間的耦合,讓物件之間的呼叫關係更加靈活。
命令模式(Command Pattern)可以對傳送者和接收者完全解耦,傳送者與接收者之間沒有直接引用關係,傳送請求的物件只需要知道如何傳送請求,而不必知道如何完成請求。
命令模式將一個請求封裝為一個物件,從而使我們可用不同的請求對客戶進行引數化;對請求排隊或者記錄請求日誌,以及支援可撤銷的操作。
模式結構
-
Invoker:呼叫者,傳送命令
-
Receiver:接收者,接收命令
-
Command:抽象命令類,定義了所有的命令
-
ConcreteCommand:具體命令類,呼叫接收者的操作
舉例說明
現有許多家電(電燈、電視機、空調....),每個家電都有自己的控制裝置,如果需要控制它們,需要逐個開啟、逐個關閉,這時候,如果有一個萬能遙控器(如下如所示),操作不同的家電只需要按對應的按鈕即可,如何使用命令模式實現?
1、建立電燈的命令接收者
public class LightReceiver {
public void on() {
System.out.println("電燈開啟了");
}
public void off() {
System.out.println("電燈關閉了");
}
}
2、建立抽象的命令介面
public interface Command {
/**
* 執行命令
*/
void execute();
/**
* 撤銷命令
*/
void undo();
}
3、建立電燈的開啟、關閉命令類,實現命令介面
public class LightOnCommand implements Command {
private LightReceiver lightReceiver;
public LightOnCommand(LightReceiver lightReceiver) {
this.lightReceiver = lightReceiver;
}
@Override
public void execute() {
lightReceiver.on();
}
@Override
public void undo() {
lightReceiver.off();
}
}
public class LightOffCommand implements Command {
private LightReceiver lightReceiver;
public LightOffCommand(LightReceiver lightReceiver) {
this.lightReceiver = lightReceiver;
}
@Override
public void execute() {
lightReceiver.off();
}
@Override
public void undo() {
lightReceiver.on();
}
}
4、建立空的命令類,用於初始化按鈕
public class NullCommand implements Command {
@Override
public void execute() { }
@Override
public void undo() { }
}
5、建立命令呼叫者
public class RemoteController {
/**
* 操作物件的個數
*/
private static final int COUNT = 5;
/**
* 開啟命令組
*/
Command[] onCommands;
/**
* 關閉命令組
*/
Command[] offCommands;
/**
* 執行撤銷的命令
*/
Command undoCommand;
/**
* 初始化
*/
public RemoteController() {
onCommands = new Command[COUNT];
offCommands = new Command[COUNT];
for (int i = 0; i < COUNT; i++) {
onCommands[i] = new NullCommand();
offCommands[i] = new NullCommand();
}
}
/**
* 設定按鈕
*/
public void setCommand(int no, Command onCommand, Command offCommand) {
onCommands[no] = onCommand;
offCommands[no] = offCommand;
}
/**
* 按下開按鈕
*/
public void pressOnButton(int no) {
onCommands[no].execute();
//記錄按鈕,以便撤銷
undoCommand = onCommands[no];
}
/**
* 按下關按鈕
*/
public void pressOffButton(int no) {
offCommands[no].execute();
//記錄按鈕,以便撤銷
undoCommand = offCommands[no];
}
/**
* 按下撤銷按鈕
*/
public void pressUndoButton(int no) {
if (undoCommand != null) {
undoCommand.undo();
}
}
}
6、測試類
public class Client {
@Test
public void testLight() {
//建立命令接收者
LightReceiver lightReceiver = new LightReceiver();
//建立電燈的一組操作(開和關)
LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);
LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);
//建立命令呼叫者
RemoteController remoteController = new RemoteController();
//設定命令
remoteController.setCommand(0, lightOnCommand, lightOffCommand);
//執行命令
System.out.println("----按下開燈按鈕----");
remoteController.pressOnButton(0);
System.out.println("----按下關燈按鈕----");
remoteController.pressOffButton(0);
System.out.println("----按下撤銷按鈕----");
remoteController.pressUndoButton(0);
}
}
7、執行結果
----按下開燈按鈕----
電燈開啟了
----按下關燈按鈕----
電燈關閉了
----按下撤銷按鈕----
電燈開啟了
8、如果再新增一個電視機,不需要改動任何已有的程式碼,新增 TvReceiver
、TvOnCommand
、TvOffCommand
這幾個類即可。
模式分析
? 優點:
- 降低系統的耦合度
- 新的命令可以很容易的加入到系統中
- 容易設計一個命令佇列。只要把命令物件放到佇列,就可以多執行緒的執行命令
? 缺點:
- 可能會導致某些系統有過多的具體命令類。因為針對每一個命令都需要設計一個具體命令類,因此某些系統可能需要大量具體命令類,這將影響命令模式的使用。
? 適用環境:
- 系統需要將請求呼叫者和請求接收者解耦,使得呼叫者和接收者不直接互動。
- 系統需要在不同的時間指定請求、將請求排隊和執行請求。
- 系統需要支援命令的撤銷(Undo)操作和恢復(Redo)操作。
- 系統需要將一組操作組合在一起,即支援巨集命令。
? 實際應用場景:
- 介面的一個按鈕都是一個命令
- 模擬CMD(DOS命令)
- 訂單的撤銷 / 恢復
- 觸發 - 反饋機制
在 JdbcTemplate 中的應用
在 Spring 的 JdbcTemplate 這個類中有 query() 方法,query() 方法中定義了一個內部類 QueryStatementCallback,QueryStatementCallback 又實現了 StatementCallback 介面,另外還有其它類實現了該介面,StatementCallback 介面中又有一個抽象方法 doInStatement()。在 execute() 中又呼叫了 query()。
將其關係縷一縷就是: