[學習筆記]設計模式之Command

爲方便讀者,本文已添加至索引:html

寫在前面

在上篇Chain of Responsibility(職責鏈)模式筆記中,咱們學習了一種行爲型設計模式。今天,咱們繼續這一主題,來學習下Command(命令)模式。能夠看到職責鏈模式是對處理請求的對象(職能者)進行了建模,而Command模式的最大不一樣之處就在於,它是對請求自己進行建模的。這一點從它的名字就能夠看出。因此它又有別名叫:Action(動做)、Transaction(事物)模式。設計模式

老規矩,咱們首先直觀地去理解什麼是命令模式。平常工做中,咱們經常會接受到一些「任務」,這些任務每每伴隨着具體要求,並對應着特定的執行人。好比說咱們作一個項目,我負責其中一個Feature。那麼以後,上頭提出的關於這個Feature的任何修改的「command」,都會讓我去執行,由於我擁有開發這個Feature所必需的相關信息。app

實現這一模型的關鍵點就在於Command的抽象類,它定義了一個執行操做的接口,好比說execute()操做。具體的一個Command子類將接收者做爲它的一個實例變量保存,並實現execute()操做,指定接收者行動起來。咱們將在示例部分進行更深刻地實例講解,那麼在此以前咱們能夠了解下它的基本要點:學習

要點梳理

  • 目的分類
    • 對象行爲型模式
  • 範圍準則
    • 對象(該模式處理對象間的關係,這些關係在運行時刻是能夠變化的,更具動態性)
  • 主要功能
    • 將一個請求封裝爲一個對象,從而使你可用不一樣的請求對客戶進行參數化;對請求排隊或記錄請求日誌,以及支持可撤消的操做
  • 適用狀況
    • 當咱們須要抽象出待執行的動做以參數化某對象時。
    • 在不一樣的時刻指定、排列和執行請求。
    • 須要支持取消操做。
    • 須要支持修改日誌,這樣當系統崩潰時,這些修改能夠被重作一遍。
    • 用構建在原語操做上的高層操做構造一個系統。
  • 參與部分
    • Command:聲明執行操做的接口
    • ConcreteCommand:將一個接收者對象綁定於一個動做;調用接收者相應的操做,以實現execute
    • Client:建立一個具體命令對象並設定它的接收者
    • Invoke:要求該命令執行這個請求
    • Receiver:知道如何實施與執行一個請求相關的操做。任何類均可能做爲一個接收者
  • 協做過程
    • Client建立一個ConcreteCommand對象並指定它的Reciever對象
    • 某個Invoker對象存儲ConcreteCommand對象
    • Invoker經過調用Command對象的execute操做來提交一個請求。若該命令是能夠取消的,那麼ConcreteCommand就在執行execute以前存儲當前狀態以用於撤銷
    • ConcreteCommand對象對調用它的Receiver的一些操做執行該請求
  • UML圖

示例分析 - 魔法小人偶的驅動器

讓咱們回顧下女巫格琳達的私人訂製魔法小人偶吧(詳見Composite模式筆記)。它經過某種神祕的傳感器做爲Input裝置,接收人們下達的命令,而後執行。爲了簡單起見,咱們今天舉一個具體的「武鬥人偶」的例子。這種人偶儼然一位空手格鬥大師,爲了靈活地展示武鬥技巧,它分別有對應於手臂和腿部的驅動裝置(ArmDrive & LegDrive),人們能夠給它下達諸如「揮擊」(Punch)、「飛踢」(FlyingKick) 等命令。爲了加強人偶功能的擴展性,咱們固然但願它往後能夠執行更多更多的命令。Command設計模式的引入幫咱們很好地解決了需求。它使得調用操做的對象與知道如何實現該操做的對象解耦。咱們的例子中,人偶的傳感器就是調用操做的對象,而各類驅動器則是實現具體操做的對象,Command模式使得傳感器不須要知道任何驅動器的信息,這很是有助於咱們往後開發更多的驅動裝置。spa

首先來看最重要的Command類:設計

 1 // ... Direction definitions ...
 2 #define NONE    0
 3 #define D_SOUTH 1
 4 #define D_NORTH 2
 5 #define D_WEST  3
 6 #define D_EAST  4
 7 
 8 // ... Abstract Command class ...
 9 class Command {
10 public:
11     virtual ~Command();
12     virtual void execute() = 0; 13 protected:
14     Command();
15 };

接下來是PunchCommand,它會令ArmDrive對象朝用戶指定的方向來狠狠重擊。注意,PunchCommand的構造器須要一個ArmDrive對象做爲參數。askDirection是提示用戶告訴它具體的方向。日誌

 1 // Punch !
 2 class PunchCommand : public Command {
 3 public:
 4     PunchCommand(ArmDrive*);
 5     
 6     virtual void execute();  7 protected:
 8     virtual int askDirection();
 9 private:
10     ArmDrive* _drive; 11 };
12 
13 PunchCommand::PunchCommand(ArmDrive* a) {
14     _drive = a;
15 }
16 
17 void PunchCommand::execute() {
18     int direction = askDirection();
19     
20     if (direction) {
21         _drive->punch(direction);
22     }
23 }

同理咱們有KickCommand須要一個LegDrive對象做爲其接收者。其中askHeight是詢問用戶須要踢擊的高度,必要的時候它得跳起來。code

 1 // Kick !
 2 class KickCommand : public Command {
 3 public:
 4     KickCommand(LegDrive*);
 5     
 6     virtual void execute();  7 protected:
 8     virtual int askHeight();
 9     virtual int askDirection();
10 private:
11     LegDrive* _drive; 12 };
13 
14 KickCommand::PunchCommand(LegDrive* l) {
15     _drive = l;
16 }
17 
18 void KickCommand::execute() {
19     int direction = askDirection();
20     int height = askHeight();
21     
22     if (direction && height > 0) {
23         _drive->kick(direction, height);
24     }
25 }

這樣一來,咱們的傳感器就能夠經過接收並調用命令來使得小人偶動起來了:htm

1 // ... Sensor Process ...
2 ArmDrive* arm = ArmDrive::getInstance();
3 LegDrive* leg = LegDrive::getInstance();
4 Command* aCommand = new PunchCommand(arm);
5 aCommand->execute();    // execute command.
6 delete aCommand;
7 aCommand = new KickCommand(leg);
8 aCommand->execute();    // execute command.

Command模式的好處還不只限於此。你們必定熟悉批處理、宏等概念吧。結合Composite設計模式(請見索引),咱們能夠設計MacroCommand,從而批量執行一系列的命令。而對於MarcroCommand自己來講,它也無須要知道其各個子命令的具體執行者,由於它只需調用子命令便可。它具備add和remove方法來管理子命令。對象

 1 // ... Macro Command ...
 2 class MacroCommand : public Command {
 3 public:
 4     MacroCommand();
 5     virtual ~MacroCommand();
 6     
 7     virtual void add(Command*);  8     virtual void remove(Command*);  9     
10     virtual void execute(); 11 private:
12     List<Command*>* _cmds;
13 };
14 
15 void MacroCommand::execute() {
16     ListIterator<Command*> i (_cmds);
17     
18     for (i.first(); !i.isDone(); i.next()) {
19         Command* c = i.currentItem();
20         c->execute();
21     }
22 }
23 
24 void MacroCommand::add (Command* c) {
25     _cmds->append(c);
26 }
27 
28 void MacroCommand::remove(Command* c) {
29     _cmds->remove(c);
30 }

舉例來看,咱們想讓小人偶打出一系列拳腳組合的「連招」:

1 // ... Punch Punch  Kick and Punch ...
2 MacroCommand* aMCmd = new MacroCommand();
3 aMCmd->add(new PunchCommand(arm));
4 aMCmd->add(new PunchCommand(arm));
5 aMCmd->add(new KickCommand(leg));
6 aMCmd->add(new PunchCommand(arm));
7 aMCmd->execute();

怎麼樣,是否是很酷!讓咱們來看一下這個設計的UML圖:

特色總結

從以上咱們能夠看到,Command模式具備以下一些特色:

  1. 它將調用操做的對象與知道如何實現該操做的對象解耦;
  2. Command是頭等的對象。它們可像其餘的對象同樣被操縱和擴展;
  3. 可將多個命令裝配成一個複合命令。好比MacroCommand;
  4. 增長新的Command很容易,由於這無需改變已有的類。

寫在最後

今天的筆記就到這裏了,歡迎你們批評指正!若是以爲能夠的話,好文推薦一下,我會很是感謝的!

相關文章
相關標籤/搜索