設計模式 – 命令模式詳解及其在JdbcTemplate中的應用
基本介紹
在軟件設計中,我們經常需要向某些對象發送一些請求,但是並不知道請求的接收者是誰,也不知道被請求的操作是哪個,我們只需要在程序運行時指定具體的請求接收者即可,此時,可以使用命令模式來設計,使得請求發送者與請求接收者消除彼此之間的耦合,讓對象之間的調用關係更加靈活。
命令模式(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()。
將其關係縷一縷就是: