基本需求:
- 一套智慧家電,有照明燈、風扇、冰箱、洗衣機,我們只要在手機上安裝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 命令)訂單的撤銷/恢復、觸發-反饋機制