命令模式(Command Pattern)詳解
一、命令模式簡介
命令模式(Command Pattern) 是一種 行為型設計模式(對象行為型模式),它將一個請求封裝為一個對象,從而使你可以用不同的請求對客戶進行參數化,并支持請求的排隊、記錄日志、撤銷等操作。
別名為動作(Action)模式或事務(Transaction)模式
簡單來說:
“把一次方法調用包裝成一個對象,這樣你就可以像處理普通對象一樣來處理這些請求。”
將請求發送者和接收者完全解耦
發送者與接收者之間沒有直接引用關系
發送請求的對象只需要知道如何發送請求,而不必知道如何完成請求
相同的開關可以通過不同的電線來控制不同的電器
開關 <- -> 請求發送者
電燈<- -> 請求的最終接收者和處理者
開關和電燈之間并不存在直接耦合關系,它們通過電線連接在一起,使用不同的電線可以連接不同的請求接收者
命令模式包含以下4個角色:
Command(抽象命令類)
ConcreteCommand(具體命令類)
Invoker(調用者)
Receiver(接收者)
命令模式的本質是對請求進行封裝。
一個請求對應于一個命令,將發出命令的責任和執行命令的責任分開。
命令模式允許請求的一方和接收的一方獨立開來,使得請求的一方不必知道接收請求的一方的接口,更不必知道請求如何被接收、操作是否被執行、何時被執行,以及是怎么被執行的。
二、解決的問題類型
命令模式主要用于解決以下問題:
- 解耦請求發送者和接收者:發送者不需要知道具體執行邏輯,只需要知道如何發送“命令”。
- 支持撤銷/重做功能:通過保存命令歷史,可以輕松實現撤銷或重做操作。
- 支持事務回滾、日志記錄、隊列任務等功能:命令可以被記錄、排隊、延遲執行。(在不同的時間指定請求、將請求排隊和執行請求)
- 系統需要將一組操作組合在一起形成宏命令
三、使用場景
場景 | 示例 |
---|---|
支持撤銷操作 | 文本編輯器中的“撤銷”、“重做”按鈕 |
解耦調用方與執行方 | 遙控器控制多個家電設備 |
實現宏命令 | 執行一組命令的組合操作 |
任務隊列 | 將多個命令加入隊列異步執行 |
日志與恢復 | 記錄命令執行日志,系統崩潰后可恢復 |
四、核心角色
角色 | 描述 |
---|---|
Command(命令接口) | 定義執行命令的方法,如 execute() |
ConcreteCommand(具體命令) | 實現 Command 接口,綁定具體的接收者(Receiver)并調用其方法 |
Invoker(調用者) | 持有命令對象,負責調用命令的 execute() 方法 |
Receiver(接收者) | 實際執行命令的對象,包含業務邏輯 |
Client(客戶端) | 創建命令對象并設置接收者,將命令交給調用者執行 |
五、代碼案例(Java)
我們以“智能遙控器控制家電”為例來演示命令模式的使用。
1. 定義命令接口
// 命令接口
interface Command {void execute();void undo(); // 可選:用于撤銷操作
}
2. 創建接收者類(實際執行動作的對象)
// 燈的接收者
class Light {public void on() {System.out.println("The light is ON");}public void off() {System.out.println("The light is OFF");}
}// 風扇的接收者
class Fan {public void start() {System.out.println("The fan is STARTING");}public void stop() {System.out.println("The fan is STOPPING");}
}
3. 創建具體命令類
// 開燈命令
class LightOnCommand implements Command {private Light light;public LightOnCommand(Light light) {this.light = light;}@Overridepublic void execute() {light.on();}@Overridepublic void undo() {light.off();}
}// 關風扇命令
class FanOffCommand implements Command {private Fan fan;public FanOffCommand(Fan fan) {this.fan = fan;}@Overridepublic void execute() {fan.stop();}@Overridepublic void undo() {fan.start();}
}
4. 創建調用者(比如遙控器)
// 遙控器
class RemoteControl {private Command command;public void setCommand(Command command) {this.command = command;}public void pressButton() {command.execute();}public void pressUndo() {command.undo();}
}
5. 客戶端測試代碼
public class Client {public static void main(String[] args) {// 創建接收者Light light = new Light();Fan fan = new Fan();// 創建具體命令Command lightOn = new LightOnCommand(light);Command fanOff = new FanOffCommand(fan);// 創建調用者RemoteControl remote = new RemoteControl();// 設置命令并執行remote.setCommand(lightOn);remote.pressButton(); // 輸出:The light is ONremote.pressUndo(); // 輸出:The light is OFFSystem.out.println("----------");remote.setCommand(fanOff);remote.pressButton(); // 輸出:The fan is STOPPINGremote.pressUndo(); // 輸出:The fan is STARTING}
}
典型代碼(C++)
典型的抽象命令類代碼
abstract class Command
{public abstract void Execute();
}
典型的調用者(請求發送者)類代碼
class Invoker
{private Command command;//構造注入public Invoker(Command command){this.command=command;} public Command Command{get { return command; }//設值注入set { command = value; }
} //業務方法,用于調用命令類的方法public void Call(){command.Execute();}
}
典型的具體命令類代碼
class ConcreteCommand : Command
{private Receiver receiver; //維持一個對請求接收者對象的引用public override void Execute(){receiver.Action();//調用請求接收者的業務處理方法Action()}
}
典型的請求接收者類代碼
class Receiver
{public void Action(){//具體操作}
}
其他案例(C++)
- 為了用戶使用方便,某系統提供了一系列功能鍵,用戶可以自定義功能鍵的功能,例如功能鍵FunctionButton可以用于退出系統(由SystemExitClass類來實現),也可以用于顯示幫助文檔(由DisplayHelpClass類來實現)。
用戶可以通過修改配置文件來改變功能鍵的用途,現使用命令模式來設計該系統,使得功能鍵類與功能類之間解耦,可為同一個功能鍵設置不同的功能。
- 電視機遙控器
電視機是請求的接收者,遙控器是請求的發送者,遙控器上有一些按鈕,不同的按鈕對應電視機的不同操作。抽象命令角色由一個命令接口來扮演,有三個具體的命令類實現了抽象命令接口,這三個具體命令類分別代表三種操作:打開電視機、關閉電視機和切換頻道。顯然,電視機遙控器就是一個典型的命令模式應用實例。
六、優缺點分析
優點 | 描述 |
---|---|
? 解耦調用者與接收者 | 調用者無需了解具體執行細節,只需調用命令即可 |
? 支持撤銷/重做、宏命令、日志記錄等高級功能 | 通過命令對象可以輕松實現這些功能,可以比較容易地設計一個命令隊列或宏命令(組合命令)為請求的撤銷(Undo)和恢復(Redo)操作提供了一種設計和實現方案 |
? 擴展性好 | 新增命令只需添加新的 ConcreteCommand 類,符合開閉原則 |
缺點 | 描述 |
---|---|
? 增加類的數量 | 每個命令都需要一個類,可能造成類爆炸 |
? 理解成本高 | 對于新手來說,結構略復雜,需要一定學習成本 |
? 性能開銷 | 如果頻繁創建命令對象,可能帶來額外內存消耗 |
七、與其他模式對比(補充)
模式名稱 | 目標 |
---|---|
策略模式 | 封裝算法,讓算法可替換,強調“行為切換” |
觀察者模式 | 當對象狀態改變時通知所有監聽者 |
命令模式 | 將請求封裝成對象,便于傳遞、存儲、撤銷等操作 |
八、最終小結
命令模式是一種非常靈活且強大的行為型設計模式,它適用于:
- 需要解耦請求發送者和接收者的場景;
- 需要支持撤銷、重做、日志記錄等高級功能;
- 需要構建任務隊列、宏命令、批處理機制等系統功能。
📌 一句話總結:
命令模式就像“遙控器”,你按下按鈕,背后誰在干活你不知道,但你知道事情能辦成。
? 推薦使用方式:
- 在需要撤銷/重做的系統中優先考慮命令模式;
- 結合“命令歷史”實現多級撤銷;
- 使用“宏命令”批量執行多個命令;
- 用 JSON 序列化保存命令狀態,實現持久化。
九、擴展
實現命令隊列
動機:
- 當一個請求發送者發送一個請求時,有不止一個請求接收者產生響應,這些請求接收者將逐個執行業務方法,完成對請求的處理。
- 增加一個CommandQueue類,由該類負責存儲多個命令對象,而不同的命令對象可以對應不同的請求接收者。
- 批處理。
實現:
using System.Collections.Generic;
namespace CommandSample
{class CommandQueue{//定義一個List來存儲命令隊列private List<Command> commands = new List<Command>();public void AddCommand(Command command){commands.Add(command);}public void RemoveCommand(Command command){commands.Remove(command);}//循環調用每一個命令對象的Execute()方法public void Execute() {foreach (object command in commands) {((Command)command).Execute();}}}
}
記錄請求日志
動機:
將請求的歷史記錄保存下來,通常以日志文件(Log File)的形式永久存儲在計算機中
- 為系統提供一種恢復機制
- 可以用于實現批處理
- 防止因為斷電或者系統重啟等原因造成請求丟失,而且可以避免重新發送全部請求時造成某些命令的重復執行
實現:
將發送請求的命令對象通過序列化寫到日志文件中
命令類必須使用屬性[Serializable]標記為可序列化
實現撤銷操作
實例:
可以通過對命令類進行修改使得系統支持撤銷(Undo)操作和恢復(Redo)操作。
設計一個簡易計算器,該計算器可以實現簡單的數學運算,還可以對運算實施撤銷操作。
宏命令
動機:
- 宏命令(Macro Command)又稱為組合命令(Composite Command),它是組合模式和命令模式聯用的產物
- 宏命令是一個具體命令類,它擁有一個集合,在該集合中包含了對其他命令對象的引用
- 當調用宏命令的Execute()方法時,將遞歸調用它所包含的每個成員命令的Execute()方法。一個宏命令的成員可以是簡單命令,還可以繼續是宏命令
- 執行一個宏命令將觸發多個具體命令的執行,從而實現對命令的批處理
部分內容由AI大模型生成,注意識別!