14.java設計模式之命令模式

xiaokantianse發表於2020-11-25

基本需求:

  • 一套智慧家電,有照明燈、風扇、冰箱、洗衣機,我們只要在手機上安裝app就可以控制對這些家電工作
  • 這些智慧家電來自不同的廠家,我們不想針對每一種家電都安裝一個App分別控制,我們希望只要一個app就可以控制全部智慧家電
  • 要實現一個app控制所有智慧家電的需要,則每個智慧家電廠家都要提供一個統一的介面給app呼叫,這時就可以考慮使用命令模式
  • 命令模式可將“動作的請求者”從“動作的執行者”物件中解耦出來
  • 也就是說,每一種家電都有開和關兩種操作,一組命令

基本介紹:

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

  • 命令模式使得請求傳送者與請求接收者消除彼此之間的耦合,讓物件之間的呼叫關係更加靈活,實現解耦

  • 命令模式中,會將一個請求封裝為一個物件,以便使用不同引數來表示不同的請求(即命令),同時命令模式也必須支援可撤銷的操作

  • 將軍釋出命令,士兵去執行。其中有幾個角色:將軍(命令釋出者)、士兵(命令的具體執行者)、命令(連線將軍和士兵)

    • Invoker 是呼叫者(將軍),Receiver 是被呼叫者(士兵),MyCommand 是命令,實現了 Command 介面,持有接收物件
  • UML類圖(原理)

    • 說明
      • Invoker:命令呼叫者角色,聚合命令
      • Command:是命令介面
      • ConcreteCommand:命令的具體實現,聚合命令的接受者
      • Receiver:命令的接受者(執行者)
  • UML類圖(案例)

  • 程式碼實現

    • public class LightReceiver {
      
         // 電燈命令的執行者
      
         public void on() {
             System.out.println("電燈開啟了");
         }
      
         public void off() {
             System.out.println("電燈關閉了");
         }
      
      }
      
    • public interface Command {
      
         // 命令介面
      
         // 執行命令和撤銷命令
         void execute();
      
         void undo();
      
      }
      
      // 子類一 電燈開命令
      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();
         }
      
      }
      
      // 子類二 電燈關命令
      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();
         }
      
      }
      
      // 子類三 空命令 對命令介面空實現
      class NoCommand implements Command {
      
         @Override
         public void execute() {
      
         }
      
         @Override
         public void undo() {
      
         }
      
      }
      
    • // 命令呼叫者
      public class RemoteController {
      
         // 開命令的集合
         private Command[] onCommands;
      
         // 關命令的集合
         private Command[] offCommands;
      
         // 記錄上一個執行的命令,便於進行撤銷
         private Command undoCommand;
      
         public RemoteController() {
             // 預設初始化五組開關 一組開關有開和關兩個按鈕,一一對應
             this.onCommands = new Command[5];
             this.offCommands = new Command[5];
             for (int i = 0; i < 5; i++) {
                 // 將開關命令的值初始化成空命令,防止空指標異常
                 this.onCommands[i] = new NoCommand();
                 this.offCommands[i] = new NoCommand();
             }
             this.undoCommand = new NoCommand();
         }
      
         // 為某組開關設定命令
         public void setCommand(int index, Command onCommand, Command offCommand) {
             if (index >= 0 && index < this.onCommands.length) {
                 this.onCommands[index] = onCommand;
                 this.offCommands[index] = offCommand;
             }
         }
      
         // 按下開的按鈕,傳送命令執行
         public void onButtonWasPushed(int index) {
             if (index >= 0 && index < this.onCommands.length) {
                 this.onCommands[index].execute();
                 // 設定撤銷命令
                 this.undoCommand = this.onCommands[index];
             }
         }
      
         // 按下關的按鈕,傳送命令執行
         public void offButtonWasPushed(int index) {
             if (index >= 0 && index < this.onCommands.length) {
                 this.offCommands[index].execute();
                 // 設定撤銷命令
                 this.undoCommand = this.offCommands[index];
             }
         }
      
         // 按下撤銷按鈕,傳送命令執行,只支援撤銷一次
         public void undoButtonWasPushed() {
             this.undoCommand.undo();
             // 重置撤銷命令
             this.undoCommand = new NoCommand();
         }
      
      }
      
      
    • public class Client {
         public static void main(String[] args) {
             // 建立執行者
             LightReceiver lightReceiver = new LightReceiver();
             // 建立一組電燈開關的命令,並設定執行者
             Command lightOnCommand = new LightOnCommand(lightReceiver);
             Command lightOffCommand = new LightOffCommand(lightReceiver);
             // 建立命令的傳送者,並設定電燈這一組命令
             RemoteController remoteController = new RemoteController();
             remoteController.setCommand(0, lightOnCommand, lightOffCommand);
             // 傳送電燈命令 執行
             System.out.println("------傳送電燈開命令------");
             remoteController.onButtonWasPushed(0);
             System.out.println("------傳送電燈關命令------");
             remoteController.offButtonWasPushed(0);
             System.out.println("------傳送撤銷命令------");
             remoteController.undoButtonWasPushed();
      
             // 使用命令模式對命令進行了封裝,將命令的釋出者和執行者進行了鬆耦合,利於系統的擴充套件
             // 比如再有一組電視的命令,直接建立新的命令類,建立其物件將其設定給命令的釋出者即可,原有的程式碼不需要改變
         }
      }
      

spring原始碼:

  • 在spring的JdbcTemplate類中就使用到了命令模式

  • // 在JdbcTemplate的query和execute方法中有如下程式碼
    public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
       ......
       // StatementCallback是一個介面,只有一個doInStatement方法,相當於命令介面
       // QueryStatementCallback區域性內部類 實現了StatementCallback,相當於具體的命令的,另外,在此處還充當了命令的執行者
       // StatementCallback介面共有四個實現類,均在JdbcTemplate某個方法內部,作為區域性內部類
       // BatchUpdateStatementCallback、UpdateStatementCallback、QueryStatementCallback、ExecuteStatementCallback
       class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
           ......
           public T doInStatement(Statement stmt) throws SQLException {
               
           }
           ......
       }
       // 呼叫execute方法
       return this.execute((StatementCallback)(new QueryStatementCallback()));   
    }
    
    // 在execute方法內部呼叫了命令介面中的方法,而execute方法又屬於JdbcTemplate,即JdbcTemplate為命令的呼叫者
    public <T> T execute(StatementCallback<T> action) throws DataAccessException {
       ......
       T result = action.doInStatement(stmt);
       ......
    }
    

注意事項:

  • 主要解決:在軟體系統中,行為請求者與行為實現者通常是一種緊耦合的關係,但某些場合,比如需要對行為進行記錄、撤銷或重做、事務等處理時,這種無法抵禦變化的緊耦合的設計就不太合適

  • 命令模式是一種資料驅動的設計模式,屬於行為型模式,請求以命令的形式包裹在物件中,並傳給呼叫物件。呼叫物件尋找可以處理該命令的合適的物件,並把該命令傳給相應的物件,該物件執行命令

  • 將發起請求的物件與執行請求的物件解耦。發起請求的物件是呼叫者,呼叫者只要呼叫命令物件的 execute()方法就可以讓接收者工作,而不必知道具體的接收者物件是誰、是如何實現的,命令物件會負責讓接收者執行請求的動作,也就是說:”請求發起者”和“請求執行者”之間的解耦是通過命令物件實現的,命令物件起到了紐帶橋樑的作用

  • 容易設計一個命令佇列,只要把命令物件放到列隊,就可以多執行緒的執行命令

  • 容易實現對請求的撤銷和重做

  • 命令模式不足:可能導致某些系統有過多的具體命令類,增加了系統的複雜度,這點在在使用的時候要注意

  • 空命令也是一種設計模式,它為我們省去了判空的操作,在上面的例項中,如果沒有用空命令,我們每按下一個按鍵都要判空,這給我們編碼帶來一定的麻煩

  • 命令模式經典的應用場景:介面的一個按鈕都是一條命令、模擬 CMD(DOS 命令)訂單的撤銷/恢復、觸發-反饋機制

相關文章