COMMAND 模式——諸葛亮造木牛流馬
junguo
Command模式,中文名稱是命令模式。該模式的目的是將不同的請求封裝成不同的對象,這樣可以用來做請求隊列,請求日志,以及撤銷的操作。該模式的核心是把請求封裝成對象,這里的請求有些不好理解,我們還是看完例子后再說這個。先看例子。
這次沒找到太好的例子,只好把木牛流馬改造一下來說明我們的例子。《三國演義》上介紹諸葛亮造過這樣的東西,相當于一個永動機了,理論上是完全不可能的事情。不過諸葛亮是神仙,老羅說他有這種能耐就算有吧。我們來看看要實現的功能,假設我們在屏幕上畫出一頭牛出來,用鼠標點一下它的嘴,可以噴火出來,再點一下,可以停止;點一下它的眼睛,可以放光,再點一下停止;點一下它的角,持續做頂人狀,再點一下停止。(這段寫的有些啰嗦,大家理解意思就好了。寫到這里,再看,自己描述的是牛魔王的形象,呵呵,不過標題不改了)點擊牛的其它部位也會發生其它相應的動作。
考慮一下,如何去做這些點擊動作(這里只考慮這些點擊動作的實現)。一般來說,結構化的方式,會為各個動作寫一個函數,在這些函數里實現這些功能。如果我們的程序不需要擴展,這樣寫,其實也無所謂。來想想,如果我們需要為所有這些操作加上撤銷操作該怎么辦呢?我現在維護的系統是這樣做的,新添加一個專門維護撤銷操作的類,然后把每次操作的內容生成一個該類的對象,保存起來。這樣一來,當對一個操作進行修改的時候,就需要對兩個類進行處理。是否可以把這樣的操作放到一個類中呢?這樣修改起來更方便一些。我們可以用Command來實現這樣的功能,也就是說把所有的操作都通過繼承Command來實現。我不知道如何能說的更好一些。還是看例子吧。
~~~
//提煉一個Command接口
class Command
{
public:
virtual ~Command(){}
virtual void Execute() = 0;
protected:
Command(){}
};
class LightOnCommand : public Command
{
public:
void Execute()
{
//一般來說這里多是借助其它對象來完成,這個例子從簡了
cout << "開始發光" << endl;
}
};
class LightOffCommand : public Command
{
public:
void Execute()
{
cout << "停止發光" << endl;
}
};
class FireOnCommand : public Command
{
public:
void Execute()
{
cout << "開始噴火" << endl;
}
};
class FireOffCommand : public Command
{
public:
void Execute()
{
cout << "結束噴火" << endl;
}
};
~~~
有了命令的代碼,我們再來看看該如何組織這樣的代碼。
~~~
//公牛類
class Bulls
{
private:
//Command數組
vector m_Commands;
public:
Bulls()
{
m_Commands.push_back(new LightOnCommand());
m_Commands.push_back(new LightOffCommand());
m_Commands.push_back(new FireOnCommand());
m_Commands.push_back(new FireOffCommand());
}
//點擊張開嘴
void ClickMouthOpen()
{
m_Commands[0]->Execute();
}
void ClickMouthClose()
{
m_Commands[1]->Execute();
}
void ClickEyeOpen()
{
m_Commands[2]->Execute();
}
void ClickEyeClose()
{
m_Commands[3]->Execute();
}
};
~~~
這里通過vecotr把需要的Command組織了起來,然后點擊張開嘴等操作的時候,通過調用相應的Command來實現。其實到現在,也沒看到Command的好處。設想一下,我們需要為操作提供一個撤消功能。該如何實現?沒有Command你也許需要再提供一個類來完成該功能,有了Command,我們可以通過以下的方式實現:
~~~
//提煉一個Command接口
class Command
{
public:
virtual ~Command(){}
virtual void Execute() = 0;
//撤消操作
virtual void UnExecute() = 0;
protected:
Command(){}
};
class LightOnCommand : public Command
{
public:
void Execute()
{
//一般來說這里多是借助其它對象來完成,這個例子從簡了
cout << "開始發光" << endl;
}
void UnExecute()
{
cout << "撤銷發光操作" << endl;
}
};
~~~
在Command中增加一個撤消操作UnExecute,所有的子類實現該功能就可以了。我們再來看看撤消操作的組織。
~~~
//公牛類
class Bulls
{
private:
vector m_Commands;
//添加一個堆棧作為撤消操作的容器
stack m_UndoCommands;
public:
Bulls()
{
m_Commands.push_back(new LightOnCommand());
m_Commands.push_back(new LightOffCommand());
m_Commands.push_back(new FireOnCommand());
m_Commands.push_back(new FireOffCommand());
}
void ClickMouthOpen()
{
m_Commands[0]->Execute();
//每次操作后,把該操作放入堆棧中
m_UndoCommands.push(m_Commands[0]);
}
void ClickMouthClose()
{
m_Commands[1]->Execute();
m_UndoCommands.push(m_Commands[1]);
}
void ClickEyeOpen()
{
m_Commands[2]->Execute();
m_UndoCommands.push(m_Commands[2]);
}
void ClickEyeClose()
{
m_Commands[3]->Execute();
m_UndoCommands.push(m_Commands[3]);
}
void Undo()
{
//撤消通過調用m_UndoCommands來完成
if ( !m_UndoCommands.empty())
{
m_UndoCommands.top()->UnExecute();
m_UndoCommands.pop();
}
}
};
~~~
這里還是比較簡單,其實這里你可以再把Redo操作加進去。再看看它的調用:
~~~
int main(int argc, char* argv[])
{
Bulls bulls;
bulls.ClickMouthOpen();
bulls.ClickEyeOpen();
bulls.ClickMouthClose();
bulls.ClickEyeClose();
bulls.Undo();
bulls.Undo();
bulls.Undo();
return 0;
}
~~~
Command是經常要用到的一個模式,一般支持撤消重做的地方都可以用到它。一般的文本,圖形編輯器中都能看到它的影子。理解起來其實并不難,關鍵是如何去用了。
參考書目:
1, 設計模式——可復用面向對象軟件的基礎(Design Patterns ——Elements of Reusable Object-Oriented Software) Erich Gamma 等著 李英軍等譯 機械工業出版社
2, Head First Design Patterns(影印版)Freeman等著 東南大學出版社
3, 道法自然——面向對象實踐指南 王詠武 王詠剛著 電子工業出版社