Overview of Content
本文將深入介紹命令模式(Command Pattern
),該模式是一種行為設計模式,用於將請求封裝為一個對象(物件),從而允許客戶端參數化操作。我們將從命令模式的基本概念入手,探討其使用場景以及如何定義和實現命令模式
此外,我們還將簡單討論命令模式的優缺點,並介紹了一些相關的設計原則和拓展概念,例如迪米特法則和依賴倒置原則
最後,我們會對指令模式進行實際的應用案例分析,以及一些最佳化和拓展方法的討論。 這篇文章旨在幫助讀者全面了解命令模式的概念、用途和實作方式,以及在實際專案中如何應用命令模式來提高程式碼的可維護性和擴展性。
Command 模式 - 概述
Command 命令模式是一種行為模式設計
可以用於封裝一系列的操作,而使用者只須要執行一個方法(調用者),而不必知道細節(執行者)
這有點類似於 Factory 設計 ?
Factory 設計
也會封裝細節不讓使用者知道,但Command 設計
的封裝自由度更高,會把行為(執行者)拆分,並自由讓使用者調整順序- 兩者個目的不同,
Command 模式
目標是修改行為(呼叫者跟執行者解耦),Factory 設計
則是創建一個對象
這有點類似於
Template
設計 ?
Template 設計
會按照一個模板,來操作一系列 固定 的行為,自由度較低,Command 設計
則較為自由,可隨意調整順序
Command 使用場景
● Command 命令模式 特性是高內聚,減少使用者使用類的成本
● 對請求排入佇列 (Queue),需要儲存命令的 Log 日誌
● 透過 呼叫者 Invoker
與 執行者 RealControl
進行解偶,對每一個方法封裝給呼叫者使用,使用者不會直接控制到執行者
Command 定義 & Command UML
● Command 模式定義
將請求裝成一個 對象(物件),讓用戶使用不同的請求,把客戶端請求參數化;既然參數化,那就可以把請求進行調整,像是排序、撤銷
● Command UML 核心重點類
類 | 關係 | 其他 |
---|---|---|
Invoker | 給使用者使用,需要 注入包裝後的方法,用於包裝命令 | 可記錄方法調用 |
ICommand(抽象方法) | 抽象執行方法 | 參數化 RealControl 的行為 |
ConcreateCommand | 實做命令(並不是實做,只是命令的實做),會對應 1~ 多個 RealControl 類 | 可做加強,紀錄時間等等 |
RealControl | 邏輯的底層程式 | 也稱為接收者 (Receiver) |
在標準的 Command 模式下 User 會有兩種行為
- 手動創建實際命令
RealControl 類
的實體(instance
)- 再將命令注入到
Invoker 類

Command 優缺點
● Command 設計的優點:
● 對於方法的調用屬於弱耦合、擴充性、靈活性更強,可以依據需求隨意調整調動
● Command 設計的缺點:
● 很明顯的類的膨脹(基本上是所有設計模式的通病)
● 它會加重程式的複雜度,需要思考一下業務需求是否有需要這樣設計,避免設計過度的問題
Command 實現
Command 標準實現
● 抽象調用 ICommand
接口:將執行(調用)動作抽象化,最終每個行為會成為一個參數,並讓 Invoker 聚合、操作這些行為 (參數)
命令參數化的基礎上再將其抽象化!
// ICommand.java
public interface ICommand {
void execute();
}
● 調用者 ConcreteCommand
類:1. 實作執行這個行為 Function,2. 內部存有實際邏輯(執行者)的對象 RealControl
,但並不是實際邏輯(不是實際執行者,只是實際調用者)
這個類代表了 命令參數化 的實做!
這一層把真正的命令包裝起來,可以控制命令真正執行的時間、執行方式、運行環境… 等等!
public class ConcreteCommand implements ICommand {
private final RealControl realControl;
public ConcreteCommand(RealControl realControl) {
this.realControl = realControl;
}
@Override
public void execute() {
realControl.action();
}
}
● 執行者 RealControl
類:實際邏輯 (具體操作行為)
public class RealControl {
public void action() {
System.out.println("Real Controller");
}
}
● Invoker
類:聚合 1 ~ 多個 ICommand
對象(物件),控制多個行為 (操作順序),可以有預設行為,也可以由使用者來加載自己所需的行為(ICommand
)
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 設計模式優化的目標有如下
● 迪米特:減少使用者對於RealControl
類的認知
● 依賴倒置:AbstactCommand
也就是抽象類,讓其依賴於抽象RealControl
而不是實體ConcreteRealControl
類
這裡依照最基本的 Command 設計實作一個 RPG Game 的上、下、左、右操作
A. RealControl
抽象:符合依賴倒置原則,抽象化實做指令(抽象化執行者)
// RealControl.java
public abstract class GameControl {
public abstract void action();
}
B. ConcreteRealControl
類:實際指令 實作細節
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");
}
}
C. AbstractCommand
抽象類:簡單的抽象化一個行為(抽象化調用者),準備 參數化 GameControl
的行為(也就是 RealControl
角色)
// 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);
}
D. ConcreteCommand
類:針對每個指令 (上、下、左、右) 實作,每個 指令的參數化(實際的調用者),並直接寫入實做類
// 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();
}
}
E. Invoker
類:聚合 所有要操作的指令,透過 Invoker 操作實際命令
- 符合迪米特原則(最少知識):不用讓使用者知道真正個實做類,只須知道某個指令
- 這裡使用外部 DI 指令,當然 我們也可以把指令直接創建在
Invoker
之內
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
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);
}
}
Command 拓展:添加紀錄
● 若要紀錄 Command
也十分簡單,由於我們參數化具體命令,所以只需要在 User 操做的 Invoker
類添加 LinkedList
做紀錄即可
A. 首先先修改 ICommand
(AbstractCommand
)抽象類,多出一個共用 Function,用來記錄上次移動的 Step
// 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);
}
}
B. 在 Invoker
內添加 LinkedList
做紀錄(也可以用來排隊、儲存)
使用
LinkedList
可以按照順序做 Step 移動紀錄(這個數據結構對於動態操作元素的行為較為友善,效率較高),也可以作為 Log 紀錄
// 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();
}
}
Servant 模式 - 概述
Servant 雇工(僕人)模式其實就是 命令模式的簡化版本,它會為使用者提供一組通用的功能
Servant UML
● Servant UML
Servant 類 | Command 設計類 | 說明 |
---|---|---|
Servant | Invoker | 聚合所有的實做類 |
IServiced | ICommand | 提供共用的功能界面 |
ConcreteServiced | RealControl | 真正個實做類 |
- | ConcreateCommand | Servant 設計會省略這個類,讓 Servant(Invoker )直接調用真正實做 |
● 比起命令模式,雇工模式的
Servant
可以調用到真正的實做類,命令模式中Invoker
則是調用到ConcreateCommand
的實做
Servant 實現
Servant 標準實現
A. IServiced
界面:
該界面代表對外提供統一的合約界面,類似於策略模式的抽象界面,它代表了一個原子單元(函數)
interface IServiced {
fun serviced()
}
B. ConcreteServiced
類:
實做類,會依據不同方式來達成業務需求
class FanServiced: IServiced {
override fun serviced() {
println("Open Fan ~ ")
}
}
class AirConditionServiced: IServiced {
override fun serviced() {
println("Open Air Condition ~ ")
}
}
C. Servant
類:
該類就是命令模式的調用者類(Invoker
),它會會聚多個 IServiced
,對外提供使用者一組通用的功能
class CoolDownServant {
enum class CoolDownWay constructor(val serviced: IServiced) {
FAN(FanServiced()),
AIR_CONDITION(AirConditionServiced())
}
fun serviced(way: CoolDownWay) {
way.serviced.serviced()
}
}
● 使用 Servant 類:
使用者只須面向 Servant
類就可以使用所有的功能
fun main() {
CoolDownServant().apply {
serviced(CoolDownServant.CoolDownWay.FAN)
serviced(CoolDownServant.CoolDownWay.AIR_CONDITION)
}
}
更多的物件導向設計
物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!
創建模式 Creation Patterns
● 創建模式 PK
● 創建模式 - Creation Patterns
:
創建模式用於「物件的創建」,它關注於如何更靈活、更有效地創建物件。這些模式可以隱藏創建物件的細節,並提供創建物件的機制,例如單例模式、工廠模式… 等等,詳細解說請點擊以下連結
● Singleton 單例模式 | 解說實現 | Android Framework Context Service
● Abstract Factory 設計模式 | 實現解說 | Android MediaPlayer
● Factory 工廠方法模式 | 解說實現 | Java 集合設計
● Builder 建構者模式 | 實現與解說 | Android Framwrok Dialog 視窗
● Clone 原型模式 | 解說實現 | Android Framework Intent
行為模式 Behavioral Patterns
● 行為模式 PK
● 行為模式 - Behavioral Patterns
:
行為模式關注物件之間的「通信」和「職責分配」。它們描述了一系列物件如何協作,以完成特定任務。這些模式專注於改進物件之間的通信,從而提高系統的靈活性。例如,策略模式、觀察者模式… 等等,詳細解說請點擊以下連結
● Stragety 策略模式 | 解說實現 | Android Framework 動畫
● Interpreter 解譯器模式 | 解說實現 | Android Framework PackageManagerService
● Chain 責任鏈模式 | 解說實現 | Android Framework View 事件傳遞
● Specification 規格模式 | 解說實現 | Query 語句實做
● Command 命令、Servant 雇工模式 | 實現與解說 | 物件導向設計
● Memo 備忘錄模式 | 實現與解說 | Android Framwrok Activity 保存
● Visitor 設計模式 | 實現與解說 | 物件導向設計
● Template 設計模式 | 實現與解說 | 物件導向設計
● Mediator 模式設計 | 實現與解說 | 物件導向設計
● Composite 組合模式 | 實現與解說 | 物件導向設計
● Observer 觀察者模式 | JDK Observer | Android Framework Listview
結構模式 Structural Patterns
● 結構模式 PK
● 結構模式 - Structural Patterns
:
結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結
● Decorate 裝飾模式 | 解說實現 | 物件導向設計
● Iterator 迭代設計 | 解說實現 | 物件導向設計