--- title: 'Command 命令模式' disqus: kyleAlien --- Command 命令模式 === ## Overview of Content Command 命令模式是一種**行為模式**,它將一系列的方法封裝在一個類,也就是將**請求封裝成一個物件** :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**Command 命令、Servant 雇工模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_command_servant/) ::: [TOC] ## Command 模式 - 概述 Command 命令模式是一種 **++行為模式設計++** 可以用於封裝一系列的操作,而使用者只須要執行一個方法,而不必知道細節 :::warning * **這有點類似於 Factory 設計** ? 1. `Factory` 設計 也會封裝細節不讓使用者知道,但 `Command` 設計 的封裝自由度更高,會把行為拆分,並自由讓使用者調整順序 2. 兩者個目的不同,`Command 模式` 目標是修改行為,`Factory 設計` 則是創建一個對象 ::: :::warning * **這有點類似於 `Template` 設計** ? `Template 設計` 會按照一個模板,來操作一系列 **固定** 的行為,自由度較低,`Command 設計` 則較為自由,可隨意調整順序 ::: ### Command 使用場景 * Command 命令模式 特性是高內聚,減少使用者使用類的成本 * 對請求排入佇列 (Queue),需要儲存命令的 Log 日誌 * 透過 `呼叫者 Invoker` 與 `執行者 RealControl` 進行解偶,對每一個方法封裝給呼叫者使用,使用者不會直接控制到執行者 ### Command 定義 & UML * Command 模式定義 將 **請求裝成一個 ++對象++**,讓用戶使用不同的請求,把客戶端參數化;既然參數化,那就可以把請求進行調整,像是排序、撤銷 * Command UML 核心重點類 | 類 | 關係 | 其他 | | -------- | -------- | - | | Invoker | 給使用者使用,需要 **注入包裝後的方法**,用於包裝命令 | 可記錄方法調用 | | ICommand(抽象方法) | 抽象執行方法 | **參數化 `RealControl` 的行為** | | ConcreateCommand | 實做命令(並不是實做,只是命令的實做),會對應 1~ 多個 `RealControl` 類 | 可做加強,紀錄時間等等 | | RealControl | 邏輯的底層程式 | 也稱為接收者 (Receiver) | :::info * 在標準的 `Command` 模式下 User 會有兩種行為 1. 手動創建實際命令 `RealControl 類` 的實體(`instance`) 2. 再將命令注入到 `Invoker 類` ::: > ![](https://hackmd.io/_uploads/ryk2wbBSh.png) ### Command 優缺點 * **Command 設計的優點**: * 對於方法的調用屬於弱耦合、擴充性、靈活性更強,可以依據需求隨意調整調動 * **Command 設計的缺點**: * 很明顯的類的膨脹(基本上是所有設計模式的通病) * 它會加重程式的複雜度,需要思考一下業務需求是否有需要這樣設計,避免設計過度的問題 ## Command 實現 ### Command 標準 1. **`ICommand` 接口**:將**執行動作抽象化**,最終每個行為會成為一個參數,並讓 Invoker 聚合、操作這些行為 (參數) > **命令參數化的基礎上再將其抽象化**! ```java= // ICommand.java public interface ICommand { void execute(); } ``` 2. **`ConcreteCommand` 類**:**^1.^ 實作執行這個行為 Function,^2.^ 內部存有實際邏輯的對象`RealControl`,但並 ==不是實際邏輯==** > 這個類代表了 **命令參數化** 的實做! :::success * 這一層把真正的命令包裝起來,可以控制命令真正執行的時間、執行方式、運行環境... 等等! ::: ```java= public class ConcreteCommand implements ICommand { private final RealControl realControl; public ConcreteCommand(RealControl realControl) { this.realControl = realControl; } @Override public void execute() { realControl.action(); } } ``` 3. **`RealControl` 類**:實際邏輯 (具體操作行為) ```java= public class RealControl { public void action() { System.out.println("Real Controller"); } } ``` 4. **`Invoker` 類**:聚合 1 ~ 多個 `ICommand` 對象,控制多個行為 (操作順序) ```java= public class Invoker { private final List<ICommand> commandList = new ArrayList<>(); public void addCommand(ICommand iCommand) { commandList.add(iCommand); } public void start() { for(ICommand cmd : commandList) { cmd.execute(); } } } ``` ### Command 優化 - 迪米特、依賴倒置 * **Command 設計模式優化的目標有如下** 1. **迪米特**:減少使用者對於 `RealControl` 類的認知 2. **依賴倒置**:`AbstactCommand` 也就是抽象類,讓其依賴於抽象 `RealControl` 而不是實體 `ConcreteRealControl` 類 > ![](https://hackmd.io/_uploads/r10pw-SSh.png) :::info 這裡依照最基本的 Command 設計實作一個 RPG Game 的上、下、左、右操作 ::: 1. **`RealControl` 抽象**:符合依賴倒置原則,抽象化實做指令(抽象化執行者) ```java= // RealControl.java public abstract class GameControl { public abstract void action(); } ``` 2. **`ConcreteRealControl` 類**:實際指令 **實作細節** ```java= public class TurnRight extends GameControl { @Override public void action() { System.out.println("Right"); } } // ------------------------------------------------------- public class TurnLeft extends GameControl { @Override public void action() { System.out.println("Lift"); } } // ------------------------------------------------------- public class TurnUp extends GameControl { @Override public void action() { System.out.println("Up"); } } // ------------------------------------------------------- public class TurnDown extends GameControl { @Override public void action() { System.out.println("Down"); } } ``` 3. **`AbstractCommand` 抽象類**:簡單的抽象化一個行為(抽象化調用者),準備 **參數化 `GameControl` 的行為**(也就是 `RealControl` 角色) ```java= // ICommand.java public abstract class AbstractCommand { protected GameControl gameControl; public AbstractCommand(GameControl gameControl) { this.gameControl = gameControl; } // 走的步數 public void execute(long step) { System.out.println("----------- Start command"); while (step-- > 0) { // 閉區間,0 ~ (step -1) exec(gameControl); } } // 抽象化調用者 abstract void exec(GameControl control); } ``` 4. **`ConcreteCommand` 類**:針對每個指令 (上、下、左、右) 實作,**每個 ++指令的參數化++**,並直接寫入實做類 ```java= // ConcreteCommand.java // 左轉 public class LeftCommand extends AbstractCommand { public LeftCommand() { super(new TurnLeft()); } @Override public void exec(GameControl control) { control.turnLeft(); } } // ------------------------------------------------------------ // 右轉 public class RightCommand extends AbstractCommand { public RightCommand() { super(new TurnRight()); } @Override public void exec(GameControl control) { control.turnRight(); } } // ------------------------------------------------------------ // 向上 public class UpCommand extends AbstractCommand { public UpCommand() { super(new TurnUp()); } @Override public void exec(GameControl control) { control.turnUp(); } } // ------------------------------------------------------------ // 向下 public class DownCommand extends AbstractCommand { public DownCommand() { super(new TurnDown()); } @Override public void exec(GameControl control) { control.turnDown(); } } ``` 4. **`Invoker` 類**:**聚合** 所有要操作的指令,透過 Invoker 操作實際命令 :::info * **符合迪米特原則**(最少知識) 不用讓使用者知道真正個實做類,只須知道某個指令 * 這裡使用外部 DI 指令,當然 **我們也可以把指令直接創建在 `Invoker` 之內** ::: ```java= public class Invoker { private final Map<String, AbstractCommand> map = new HashMap<>(); public Invoker(AbstractCommand right, AbstractCommand left, AbstractCommand up, AbstractCommand down) { map.put("Right", right); map.put("Left", left); map.put("Up", up); map.put("Down", down); } // 指令化 public void turnRight(long step) { AbstractCommand right = map.get("Right"); if(right == null) { return; } right.execute(step); } // 指令化 public void turnLeft(long step) { AbstractCommand left = map.get("Left"); if(left == null) { return; } left.execute(step); } // 指令化 public void turnUp(long step) { AbstractCommand up = map.get("Up"); if(up == null) { return; } up.execute(step); } // 指令化 public void turnDown(long step) { AbstractCommand down = map.get("Down"); if(down == null) { return; } down.execute(step); } } ``` * **作為 User 使用 `Command` 設計模式**:創建實作類,再創建 Invoker,將實作類注入 Invoker ```java= public class MainCommand { public static void main(String[] args) { // 將實作類注入 Invoker Invoker invoker = new Invoker( new RightCommand(), new LeftCommand(), new UpCommand(), new DownCommand()); invoker.turnDown(3); invoker.turnUp(1); invoker.turnLeft(2); invoker.turnRight(5); } } ``` > ![](https://i.imgur.com/9FMX3qZ.png) ### Command 拓展 - 添加紀錄 * 若要紀錄 `Command` 也十分簡單,由於我們參數化具體命令,所以只需要在 User 操做的 `Invoker` 類添加 `LinkedList` 做紀錄即可 1. 首先先修改 `ICommand`(`AbstractCommand`)抽象類,多出一個共用 Function,用來記錄上次移動的 Step ```java= // AbstractCommand.java public abstract class AbstractCommand { private long step; protected GameControl gameControl; public AbstractCommand(GameControl gameControl) { this.gameControl = gameControl; } public void execute(long step) { // 紀錄最後一次移動的步數 this.step = step; System.out.println("----------- Start command"); while (step-- > 0) { exec(gameControl); } } abstract void exec(GameControl control); // 新增 Function public void doLastAgain() { execute(step); } } ``` 2. 在 `Invoker` 內添加 `LinkedList` 做紀錄(也可以用來排隊、儲存) :::info 使用 `LinkedList` 可以按照順序做 Step 移動紀錄(這個數據結構對於動態操作元素的行為較為友善,效率較高),也可以作為 Log 紀錄 ::: ```java= // Invoker.java public class Invoker { private final LinkedList<AbstractCommand> record = new LinkedList<>(); private final Map<String, AbstractCommand> map = new HashMap<>(); public Invoker(AbstractCommand right, AbstractCommand left, AbstractCommand up, AbstractCommand down) { map.put("Right", right); map.put("Left", left); map.put("Up", up); map.put("Down", down); } ... 省略上、下、左、右操作 public void showRecordStep() { AbstractCommand tmp = record.getFirst(); while (tmp != null) { tmp.doLastAgain(); tmp = record.poll(); } } } ``` > ![](https://i.imgur.com/90NhL5R.png) ## 更多的物件導向設計 物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)! :::info * [**設計建模 2 大概念- UML 分類、使用**](https://devtechascendancy.com/introduction-to-uml-and-diagrams/) * [**物件導向設計原則 – 6 大原則(一)**](https://devtechascendancy.com/object-oriented-design-principles_1/) * [**物件導向設計原則 – 6 大原則(二)**](https://devtechascendancy.com/object-oriented-design-principles_2/) ::: ### 創建模式 - Creation Patterns * [**創建模式 PK**](https://devtechascendancy.com/pk-design-patterns-factory-builder-best/) * **創建模式 - `Creation Patterns`**: 創建模式用於「**物件的創建**」,它關注於如何更靈活、更有效地創建對象。這些模式可以隱藏創建對象的細節,並提供創建對象的機制,例如單例模式、工廠模式… 等等,詳細解說請點擊以下連結 :::success * [**Singleton 單例模式 | 解說實現 | Android Framework Context Service**](https://devtechascendancy.com/object-oriented_design_singleton/) * [**Abstract Factory 設計模式 | 實現解說 | Android MediaPlayer**](https://devtechascendancy.com/object-oriented_design_abstract-factory/) * [**Factory 工廠方法模式 | 解說實現 | Java 集合設計**](https://devtechascendancy.com/object-oriented_design_factory_framework/) * [**Builder 建構者模式 | 實現與解說 | Android Framwrok Dialog 視窗**](https://devtechascendancy.com/object-oriented_design_builder_dialog/) * [**Clone 原型模式 | 解說實現 | Android Framework Intent**](https://devtechascendancy.com/object-oriented_design_clone_framework/) * [**Object Pool 設計模式 | 實現與解說 | 利用 JVM**](https://devtechascendancy.com/object-oriented_design_object-pool/) * [**Flyweight 享元模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_flyweight/) ::: ### 行為模式 - Behavioral Patterns * [**行為模式 PK**](https://devtechascendancy.com/pk-design-patterns-cmd-strat-state-obs-chain/) * **行為模式 - `Behavioral Patterns`**: 行為模式關注物件之間的「**通信**」和「**職責分配**」。它們描述了一系列對象如何協作,以完成特定任務。這些模式專注於改進物件之間的通信,從而提高系統的靈活性。例如,策略模式、觀察者模式… 等等,詳細解說請點擊以下連結 :::warning * [**Stragety 策略模式 | 解說實現 | Android Framework 動畫**](https://devtechascendancy.com/object-oriented_design_stragety_framework/) * [**Interpreter 解譯器模式 | 解說實現 | Android Framework PackageManagerService**](https://devtechascendancy.com/object-oriented_design_interpreter_framework/) * [**Chain 責任鏈模式 | 解說實現 | Android Framework View 事件傳遞**](https://devtechascendancy.com/object-oriented_design_chain_framework/) * [**State 狀態模式 | 實現解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_state/) * [**Specification 規格模式 | 解說實現 | Query 語句實做**](https://devtechascendancy.com/object-oriented_design_specification-query/) * [**Command 命令、Servant 雇工模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_command_servant/) * [**Memo 備忘錄模式 | 實現與解說 | Android Framwrok Activity 保存**](https://devtechascendancy.com/object-oriented_design_memo_framework/) * [**Visitor 設計模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_visitor_dispatch/) * [**Template 設計模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_template/) * [**Mediator 模式設計 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_programming_mediator/) * [**Composite 組合模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_programming_composite/) ::: ### 結構模式 - Structural Patterns * [**結構模式 PK**](https://devtechascendancy.com/pk-design-patterns-proxy-decorate-adapter/) * **結構模式 - `Structural Patterns`**: 結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結 :::danger * [**Bridge 橋接模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_bridge/) * [**Decorate 裝飾模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_decorate/) * [**Proxy 代理模式 | 解說實現 | 分析動態代理**](https://devtechascendancy.com/object-oriented_design_proxy_dynamic-proxy/) * [**Iterator 迭代設計 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_iterator/) * [**Facade 外觀、門面模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_facade/) * [**Adapter 設計模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_adapter/) ::: ## Appendix & FAQ :::info ::: ###### tags: `Java 設計模式` `基礎進階`