重返设计模式--命令模式
理論要點(diǎn)
什么是命令模式:“將一個(gè)請(qǐng)求封裝成一個(gè)對(duì)象,從而允許你使用不同的請(qǐng)求、隊(duì)列或日志將客服端參數(shù)化,同時(shí)支持請(qǐng)求操作的撤銷與恢復(fù)。” 通俗講就是把方法的調(diào)用封裝進(jìn)對(duì)象中去回調(diào)。
優(yōu)缺點(diǎn):對(duì)類間解耦、可擴(kuò)展性強(qiáng)、易于命令的組合維護(hù)、易于與其他模式結(jié)合,而缺點(diǎn)是會(huì)導(dǎo)致類的膨脹。
使用場(chǎng)景:
1,命令模式很適合實(shí)現(xiàn)諸如撤消,重做,回放,時(shí)間倒流之類的功能。
2,基于命令模式實(shí)現(xiàn)錄像與回放等功能,也就是執(zhí)行并解析一系列經(jīng)過預(yù)錄制的序列化后的各玩家操作的有序命令集合(如游戲AI,Demo演示等)。
代碼分析
1, 我們每個(gè)游戲都會(huì)有處理玩家輸入事件,它記錄每次的輸入,并將之轉(zhuǎn)換為游戲中一個(gè)有意義的動(dòng)作,如下圖:
通常我們會(huì)這樣直觀的實(shí)現(xiàn):
好,我們來分析下上面的代碼,想想通常我們游戲的按鍵是可以自定義設(shè)置的,那么如果玩家要自定義按鍵,就不得不改這段代碼的邏輯了,調(diào)用函數(shù)交換來交換去,是不是覺得很麻煩而且容易出錯(cuò)~
2,下面我們?cè)賮磉@樣改裝下:
首先,我們定義一個(gè)基類,代表可觸發(fā)的游戲動(dòng)作命令:
然后,我們?yōu)槊總€(gè)具體的游戲動(dòng)作創(chuàng)建子類:
class JumpCommand : public Command { public:virtual void execute() { jump(); } };class FireCommand : public Command { public:virtual void execute() { fireGun(); } };...然后,回到輸入處理類中,我們?yōu)槊總€(gè)按鈕保存一個(gè)指向它的指針。
class InputHandler { public:void handleInput(); private:Command* _buttonX;Command* _buttonY;Command* _buttonA;Command* _buttonB; };接下來我們就可以把最開始的輸入處理接口改寫從這樣:
void InputHandler::handleInput() {if(isPressed(BUTTON_X)_buttonX->execute();else if(isPressed(BUTTON_Y))_buttonY->execute();else if(isPressed(BUTTON_A))_buttonA->execute();else if(isPressed(BUTTON_B))_buttonB->execute(); }恩,到目前為止,命令模式其實(shí)就這樣悄悄出現(xiàn)了,以前每個(gè)輸入直接調(diào)用一個(gè)函數(shù),而現(xiàn)在則是增加了一個(gè)間接調(diào)用層對(duì)象,簡(jiǎn)而言之,這就是命令模式。
3,注意到?jīng)],上面代碼還有這樣一個(gè)問題:如JumpCommand對(duì)象執(zhí)行的行為是直接作用到主角身上的,即它只能讓主角跳躍。
下面我們來優(yōu)化下,不讓函數(shù)去找它們控制的角色,我們將函數(shù)控制的角色對(duì)象傳進(jìn)去:
相應(yīng)的子類:
class JumpCommand : public Command { public:virtual void execute(GameActor& actor){actor.jump();} };...現(xiàn)在,我們可以使用這個(gè)類讓游戲中的任何角色跳來跳去了。在這之前我們還缺了點(diǎn)代碼,就是讓生成命令與執(zhí)行命令分開,解耦了生產(chǎn)者和消費(fèi)者:
首先是生成命令:
然后,需要一些接受命令的代碼,作用在玩家角色上。
Command* command = inputHandler.handleInput(); if(command) {command->execute(actor); }這樣我們就可以控制游戲中的任何角色,只需向命令傳入不同的角色。試想下,這個(gè)在我們游戲中可以運(yùn)用在哪里?
沒錯(cuò),角色AI,我們是不是可以這樣,讓AI代碼負(fù)責(zé)生成命令,游戲角色執(zhí)行命令。這樣在選擇命令的AI和展現(xiàn)命令的游戲角色間解耦給了我們很大的靈活度。我們可以把生成命令放入隊(duì)列中,即命令流的形式去執(zhí)行:
一些代碼(輸入控制器或者AI)產(chǎn)生一系列命令放入流中。 另一些代碼(調(diào)度器或者角色自身)調(diào)用并消耗命令。 通過在中間加入隊(duì)列,我們解耦了消費(fèi)者和生產(chǎn)者。
4,最后的這個(gè)例子是這種模式最廣為人知的使用情況。 如果一個(gè)命令對(duì)象可以做一件事,那么它亦可以撤銷這件事。 這在策略游戲中經(jīng)常使用,如《三國群英傳III》,我們走過的格子還可以撤銷回來。
下面我們先來實(shí)現(xiàn)一個(gè)簡(jiǎn)單的移動(dòng)命令:
然后是處理接口:
Command* hanleInput() {Unit* unit = getSelectedUnit();if (isPressed(BUTTON_UP)) {// 向上移動(dòng)單位int destY = unit->y() - 1;return new MoveUnitCommand(unit, unit->x(), destY);}if (isPressed(BUTTON_DOWN)) {// 向下移動(dòng)單位int destY = unit->y() + 1;return new MoveUnitCommand(unit, unit->x(), destY);}// 其他的移動(dòng)……return NULL; }好,上面就是策略游戲移動(dòng)示例的原型,我們來看看對(duì)應(yīng)的撤銷功能怎么添加:
首先在命令基類中添加撤銷接口:
然后來修改子類中的實(shí)現(xiàn):
class MoveUnitCommand : public Command { public:MoveUnitCommand(Unit* unit, int x, int y): _unit(unit),_xBefore(0),_yBefore(0),_x(x),_y(y){}virtual void execute(){// 保存移動(dòng)之前的位置// 這樣之后可以復(fù)原。_xBefore = _unit->x();_yBefore = _unit->y();_unit->moveTo(_x, _y);}virtual void undo(){_unit->moveTo(_xBefore, _yBefore);}private:Unit* _unit;int _xBefore, _yBefore;int _x, _y; };對(duì)于命令模式,這種撤銷操作是不是很簡(jiǎn)單就實(shí)現(xiàn)了。
最后,真的是最后了~來科普下怎么實(shí)現(xiàn)多重撤銷,就像我們IDE那樣按ctrl + z和ctrl + y可以靈活前后多層回檔。其實(shí)也很簡(jiǎn)單:我們不單單記錄最后一條指令,還要記錄指令列表,然后用一個(gè)引用指向“當(dāng)前”的那個(gè)。 當(dāng)玩家執(zhí)行一條命令,我們將其添加到列表,然后將代表“當(dāng)前”的指針指向它。
當(dāng)玩家選擇“撤銷”,我們撤銷現(xiàn)在的命令,將代表當(dāng)前的指針往后退。 當(dāng)他們選擇“重做”,我們將代表當(dāng)前的指針往前進(jìn),執(zhí)行該指令。 如果在撤銷后選擇了新命令,那么清除命令列表中當(dāng)前的指針?biāo)该钪蟮娜棵睢?/p>
嗯,就這樣了,結(jié)束~~
總結(jié)
以上是生活随笔為你收集整理的重返设计模式--命令模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: halcon中阈值分割算子用法
- 下一篇: 学linux作用,linux有必要学吗?