命令模式,是將一個請求封裝為一個對象,從而使我們可以用不同的請求對客戶進行參數化、對請求排隊或記錄請求日志,以及支持可撤銷的操作。
動機(Motivation)
- ”行為請求者“與”行為實現者“通常呈現一種”緊耦合“。但在某些場合——比如需要對行為進行”記錄、撤銷、事務“等處理,這種無法抵御變化的緊耦合是不合適的。
- 在這種情況下,如何將”行為請求者“與”行為實現者“解耦?將一組行為抽象為對象,可以實現二者之間的松耦合。
模式定義
將一個請求(行為)封裝成一個對象,從而使你可用不用的請求對客戶進行參數化;對請求排隊或記錄請求日志,以及支持可撤銷的操作。 ——《設計模式》GoF
要點總結
- Command模式的根本目的在於將”行為請求者“與”行為實現者“解耦,在面向對象語言中,常見的實現手段是”將行為抽象為對象“。
- 與C++中的函數對象類似,C++函數對象以函數簽名來定義行為接口規范,更靈活性能更高。
結構(Structure)
基本代碼
#include <iostream> #include <list> using namespace std; // Receiver類,知道如何實施與執行一個與請求相關的操作: class Receiver { public: void Action() { cout << "Receiver" << endl; } }; // Command類,用來聲明執行操作的接口 class Command { public: virtual void Excute() = 0; virtual void setReceiver(Receiver* r) = 0; virtual ~Command(){}; }; // ConcreteCommand類,綁定一個Receiver,調用其相應操作以實現Excute: class ConcreteCommand : public Command { private: Receiver* receiver; public: void setReceiver(Receiver* r) { receiver = r; } void Excute() { //cout << "ConcreteCommand" << endl; receiver->Action(); } }; // 要求該命令執行這個請求: class Invoker { private: list<Command* > commands; public: void setCommand(Command* c) { commands.push_back(c); } void Notify() { for (auto c = commands.begin(); c != commands.end(); c++) { (*c)->Excute(); } } }; // 客戶端實現代碼: int main() { Command* c = new ConcreteCommand(); Receiver* r = new Receiver(); c->setReceiver(r); Invoker I; i.setCommand(c); i.Notify(); // Receiver delete r; delete c; return 0; }
應用場景
優點:
- 能較容易地設計一個命令隊列;
- 在需要的情況下,可以較容易地將命令記入日志;
- 允許接收請求的一方決定是否要否決請求;
- 可以輕易地實現對請求的撤銷和重做;
- 增加新的具體命令類很方便;
- 命令模式把請求一個操作的對象與知道怎么執行一個操作的對象分割開了。