目錄
- 一、模式核心概念與結構
- 二、C++ 實現示例:遙控器與家電控制
- 三、命令模式的關鍵特性
- 四、應用場景
- 五、命令模式與其他設計模式的關系
- 六、C++ 標準庫中的命令模式應用
- 七、優缺點分析
- 八、實戰案例:數據庫事務命令
- 九、實現注意事項
- 如果這篇文章對你有所幫助,渴望獲得你的一個點贊!
命令模式(Command Pattern)是一種【行為型】設計模式,它將請求封裝為對象,從而使你可以用不同的請求對客戶端進行參數化,對請求排隊或記錄請求日志,以及支持可撤銷的操作。這種模式將發起請求的對象(客戶端)與執行請求的對象(接收者)解耦,通過命令對象作為中間層來協調兩者。
一、模式核心概念與結構
命令模式包含四個核心角色:
- 命令接口(Command):定義執行操作的接口,通常包含
execute()
方法。 - 具體命令(Concrete Command):實現命令接口,持有接收者的引用,并調用接收者的相應方法。
- 接收者(Receiver):知道如何執行與請求相關的操作,負責具體業務邏輯。
- 調用者(Invoker):持有命令對象并觸發執行,不直接與接收者交互。
二、C++ 實現示例:遙控器與家電控制
以下是一個經典的命令模式示例,演示如何用命令模式實現家電的遠程控制:
#include <iostream>
#include <string>
#include <memory>
#include <vector>// 接收者:家電
class Light {
public:void turnOn() { std::cout << "Light is on" << std::endl; }void turnOff() { std::cout << "Light is off" << std::endl; }
};class TV {
public:void turnOn() { std::cout << "TV is on" << std::endl; }void turnOff() { std::cout << "TV is off" << std::endl; }void setChannel(int channel) {std::cout << "TV channel set to " << channel << std::endl;}
};// 命令接口
class Command {
public:virtual ~Command() = default;virtual void execute() = 0;virtual void undo() = 0; // 支持撤銷操作
};// 具體命令:開燈命令
class LightOnCommand : public Command {
private:std::shared_ptr<Light> light;public:LightOnCommand(std::shared_ptr<Light> l) : light(l) {}void execute() override {light->turnOn();}void undo() override {light->turnOff();}
};// 具體命令:關燈命令
class LightOffCommand : public Command {
private:std::shared_ptr<Light> light;public:LightOffCommand(std::shared_ptr<Light> l) : light(l) {}void execute() override {light->turnOff();}void undo() override {light->turnOn();}
};// 具體命令:電視命令
class TVOnCommand : public Command {
private:std::shared_ptr<TV> tv;public:TVOnCommand(std::shared_ptr<TV> t) : tv(t) {}void execute() override {tv->turnOn();tv->setChannel(1); // 默認頻道}void undo() override {tv->turnOff();}
};// 調用者:遙控器
class RemoteControl {
private:std::shared_ptr<Command> onCommand;std::shared_ptr<Command> offCommand;std::vector<std::shared_ptr<Command>> history; // 命令歷史,用于撤銷public:void setCommands(std::shared_ptr<Command> on, std::shared_ptr<Command> off) {onCommand = on;offCommand = off;}void pressOnButton() {if (onCommand) {onCommand->execute();history.push_back(onCommand);}}void pressOffButton() {if (offCommand) {offCommand->execute();history.push_back(offCommand);}}void pressUndoButton() {if (!history.empty()) {auto lastCommand = history.back();lastCommand->undo();history.pop_back();std::cout << "Undo last command" << std::endl;} else {std::cout << "Nothing to undo" << std::endl;}}
};// 客戶端代碼
int main() {// 創建接收者auto livingRoomLight = std::make_shared<Light>();auto tv = std::make_shared<TV>();// 創建具體命令auto lightOn = std::make_shared<LightOnCommand>(livingRoomLight);auto lightOff = std::make_shared<LightOffCommand>(livingRoomLight);auto tvOn = std::make_shared<TVOnCommand>(tv);// 創建調用者RemoteControl remote;// 設置命令remote.setCommands(lightOn, lightOff);// 使用遙控器std::cout << "=== Control Light ===" << std::endl;remote.pressOnButton(); // 開燈remote.pressOffButton(); // 關燈remote.pressUndoButton(); // 撤銷:重新開燈// 切換控制對象remote.setCommands(tvOn, lightOff);std::cout << "\n=== Control TV ===" << std::endl;remote.pressOnButton(); // 開電視remote.pressUndoButton(); // 撤銷:關電視return 0;
}
三、命令模式的關鍵特性
- 請求封裝:
- 將請求(操作)封裝為命令對象,使請求可以被存儲、傳遞和調用。
- 解耦調用者和接收者:
- 調用者(如遙控器)無需知道接收者(如家電)的具體實現,只需調用命令接口。
- 支持撤銷和重做:
- 通過在命令接口中定義
undo()
方法,可以實現操作的撤銷功能。
- 通過在命令接口中定義
- 隊列和日志:
- 命令對象可以被序列化和存儲,支持請求的排隊、記錄和回放。
四、應用場景
- 撤銷 / 重做系統:
- 文本編輯器、圖形設計工具中的撤銷操作。
- 數據庫事務的回滾機制。
- 異步任務執行:
- 線程池、消息隊列中的任務調度。
- 網絡請求的異步處理。
- 菜單和按鈕系統:
- GUI 應用中的菜單項和按鈕操作。
- 游戲中的動作命令(如攻擊、跳躍)。
- 宏錄制:
- 辦公軟件中的宏功能,記錄一系列操作并回放。
- 分布式系統:
- 遠程過程調用(RPC)中的命令傳遞。
五、命令模式與其他設計模式的關系
- 備忘錄模式:
- 命令模式實現操作的撤銷,備忘錄模式保存對象的狀態。
- 兩者可結合使用,命令模式負責執行操作,備忘錄模式提供狀態恢復。
- 觀察者模式:
- 命令模式用于處理請求,觀察者模式用于事件通知。
- 命令的執行可以觸發觀察者模式中的事件。
- 責任鏈模式:
- 命令模式將請求封裝為對象,責任鏈模式將請求傳遞給多個處理者。
- 兩者可結合使用,責任鏈中的處理者可以是命令對象。
六、C++ 標準庫中的命令模式應用
- 函數對象(Functor):
std::function
和函數指針可視為輕量級命令模式實現。- 例如,
std::sort
接受一個比較函數對象作為參數。
- 線程池:
- C++11 的
std::thread
和std::async
可結合命令模式實現任務隊列。
- C++11 的
- 信號與槽機制:
- Qt 框架中的信號與槽是命令模式的典型應用,信號觸發時執行槽函數。
七、優缺點分析
優點:
- 解耦調用者和接收者:降低系統耦合度,提高可維護性。
- 支持擴展性:易于添加新的命令類,符合開閉原則。
- 支持撤銷和日志:方便實現復雜的操作管理功能。
缺點:
- 類數量增加:每個具體命令都需要一個類,可能導致類膨脹。
- 實現復雜度:對于簡單操作,使用命令模式可能顯得冗余。
- 性能開銷:封裝請求為對象會引入額外的間接性,可能影響性能。
八、實戰案例:數據庫事務命令
以下是一個數據庫事務命令的實現示例,支持事務的提交和回滾:
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <stack>// 接收者:數據庫連接
class Database {
public:void executeQuery(const std::string& query) {std::cout << "Executing query: " << query << std::endl;// 實際執行SQL查詢...}void commit() {std::cout << "Committing transaction" << std::endl;// 提交事務...}void rollback() {std::cout << "Rolling back transaction" << std::endl;// 回滾事務...}
};// 命令接口
class Command {
public:virtual ~Command() = default;virtual void execute() = 0;virtual void undo() = 0;
};// 具體命令:SQL查詢命令
class QueryCommand : public Command {
private:std::shared_ptr<Database> database;std::string query;bool isTransactional;public:QueryCommand(std::shared_ptr<Database> db, const std::string& q, bool transactional = false): database(db), query(q), isTransactional(transactional) {}void execute() override {database->executeQuery(query);}void undo() override {if (isTransactional) {database->rollback();} else {std::cout << "Non-transactional query cannot be undone" << std::endl;}}
};// 具體命令:事務提交命令
class CommitCommand : public Command {
private:std::shared_ptr<Database> database;public:CommitCommand(std::shared_ptr<Database> db) : database(db) {}void execute() override {database->commit();}void undo() override {std::cout << "Commit cannot be undone" << std::endl;}
};// 調用者:事務管理器
class TransactionManager {
private:std::shared_ptr<Database> database;std::stack<std::shared_ptr<Command>> commandStack; // 命令棧,用于撤銷public:TransactionManager(std::shared_ptr<Database> db) : database(db) {}void executeCommand(std::shared_ptr<Command> command) {command->execute();commandStack.push(command);}void commit() {auto commitCmd = std::make_shared<CommitCommand>(database);commitCmd->execute();commandStack.push(commitCmd);}void rollbackLastCommand() {if (!commandStack.empty()) {auto lastCommand = commandStack.top();lastCommand->undo();commandStack.pop();std::cout << "Rolled back last command" << std::endl;} else {std::cout << "No commands to roll back" << std::endl;}}
};// 客戶端代碼
int main() {// 創建數據庫連接auto db = std::make_shared<Database>();// 創建事務管理器TransactionManager txManager(db);// 執行一系列命令std::cout << "=== Executing commands ===" << std::endl;txManager.executeCommand(std::make_shared<QueryCommand>(db, "BEGIN TRANSACTION", true));txManager.executeCommand(std::make_shared<QueryCommand>(db, "INSERT INTO users VALUES ('Alice')"));txManager.executeCommand(std::make_shared<QueryCommand>(db, "INSERT INTO users VALUES ('Bob')"));// 回滾最后一條命令std::cout << "\n=== Rolling back last command ===" << std::endl;txManager.rollbackLastCommand();// 提交事務std::cout << "\n=== Committing transaction ===" << std::endl;txManager.commit();// 嘗試回滾提交操作(不可行)std::cout << "\n=== Trying to roll back commit ===" << std::endl;txManager.rollbackLastCommand();return 0;
}
九、實現注意事項
- 撤銷機制設計:
- 確保命令的
undo()
方法能正確恢復系統狀態。 - 對于某些不可撤銷的操作(如刪除文件),
undo()
可記錄日志或提示用戶。
- 確保命令的
- 命令參數化:
- 命令可以設計為接受參數,使其更加靈活(如
SetChannelCommand
可接受頻道號)。
- 命令可以設計為接受參數,使其更加靈活(如
- 宏命令(Composite Command):
- 可以創建包含多個命令的宏命令,實現批量操作。
- 線程安全:
- 在多線程環境中,命令的執行可能需要同步機制(如互斥鎖)。
命令模式是 C++ 中實現請求封裝和操作管理的重要工具,通過將請求轉化為對象,使系統更加靈活、可擴展,并支持復雜的功能如撤銷、隊列和日志。