設計模式 - 命令模式詳解及其在JdbcTemplate中的應用

農夫三拳有點疼~發表於2020-04-26

基本介紹

在軟體設計中,我們經常需要向某些物件傳送一些請求,但是並不知道請求的接收者是誰,也不知道被請求的操作是哪個,我們只需要在程式執行時指定具體的請求接收者即可,此時,可以使用命令模式來設計,使得請求傳送者與請求接收者消除彼此之間的耦合,讓物件之間的呼叫關係更加靈活。

命令模式(Command Pattern)可以對傳送者和接收者完全解耦,傳送者與接收者之間沒有直接引用關係,傳送請求的物件只需要知道如何傳送請求,而不必知道如何完成請求。

命令模式將一個請求封裝為一個物件,從而使我們可用不同的請求對客戶進行引數化;對請求排隊或者記錄請求日誌,以及支援可撤銷的操作。

模式結構

  • Invoker:呼叫者,傳送命令

  • Receiver:接收者,接收命令

  • Command:抽象命令類,定義了所有的命令

  • ConcreteCommand:具體命令類,呼叫接收者的操作

舉例說明

現有許多家電(電燈、電視機、空調....),每個家電都有自己的控制裝置,如果需要控制它們,需要逐個開啟、逐個關閉,這時候,如果有一個萬能遙控器(如下如所示),操作不同的家電只需要按對應的按鈕即可,如何使用命令模式實現?

設計模式 - 命令模式詳解及其在JdbcTemplate中的應用

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、如果再新增一個電視機,不需要改動任何已有的程式碼,新增 TvReceiverTvOnCommandTvOffCommand 這幾個類即可。

模式分析

? 優點:

  • 降低系統的耦合度
  • 新的命令可以很容易的加入到系統中
  • 容易設計一個命令佇列。只要把命令物件放到佇列,就可以多執行緒的執行命令

? 缺點:

  • 可能會導致某些系統有過多的具體命令類。因為針對每一個命令都需要設計一個具體命令類,因此某些系統可能需要大量具體命令類,這將影響命令模式的使用。

? 適用環境:

  • 系統需要將請求呼叫者和請求接收者解耦,使得呼叫者和接收者不直接互動。
  • 系統需要在不同的時間指定請求、將請求排隊和執行請求。
  • 系統需要支援命令的撤銷(Undo)操作和恢復(Redo)操作。
  • 系統需要將一組操作組合在一起,即支援巨集命令。

? 實際應用場景:

  • 介面的一個按鈕都是一個命令
  • 模擬CMD(DOS命令)
  • 訂單的撤銷 / 恢復
  • 觸發 - 反饋機制

在 JdbcTemplate 中的應用

在 Spring 的 JdbcTemplate 這個類中有 query() 方法,query() 方法中定義了一個內部類 QueryStatementCallback,QueryStatementCallback 又實現了 StatementCallback 介面,另外還有其它類實現了該介面,StatementCallback 介面中又有一個抽象方法 doInStatement()。在 execute() 中又呼叫了 query()。

將其關係縷一縷就是:

相關文章