5.2 COMMAND(命令) — 對(duì)象行為型模式

1 意圖

將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象,從而使你可用不同的請(qǐng)求對(duì)客戶(hù)進(jìn)行參數(shù)化;對(duì)請(qǐng)求排隊(duì)或記錄請(qǐng)求日志,以及支持可撤消的操作。

2 別名

動(dòng)作( Action ),事務(wù)( Transaction )

3 動(dòng)機(jī)

有時(shí)必須向某對(duì)象提交請(qǐng)求,但并不知道關(guān)于被請(qǐng)求的操作或請(qǐng)求的接受者的任何信息。例如,用戶(hù)界面工具箱包括按鈕和菜單這樣的對(duì)象,它們執(zhí)行請(qǐng)求響應(yīng)用戶(hù)輸入。但工具箱,不能顯式的在按鈕或菜單實(shí)現(xiàn)該請(qǐng)求,因?yàn)橹挥惺褂霉ぞ呦涞膽?yīng)用知道該由哪個(gè)對(duì)象做哪個(gè)操作。而工具箱的設(shè)計(jì)者無(wú)法知道請(qǐng)求的接受者或執(zhí)行的操作。

命令模式通過(guò)將請(qǐng)求本身變成一個(gè)對(duì)象使工具箱對(duì)象可向未指定的應(yīng)用對(duì)象提出請(qǐng)求。這個(gè)對(duì)象可被存儲(chǔ)并像其他的對(duì)象一樣被傳遞。這一模式的關(guān)鍵是一個(gè)抽象的Command類(lèi),它定義了一個(gè)執(zhí)行操作的接口。其最簡(jiǎn)單的形式是一個(gè)抽象的Excute操作。具體的Command子類(lèi)將接受者作為其一個(gè)實(shí)例變量,并實(shí)現(xiàn)Execute操作,指定接收站采取的動(dòng)作。而接收者有執(zhí)行該請(qǐng)求所需的具體信息。


image.png

用Command對(duì)象可以可很容易的實(shí)現(xiàn)菜單(Menu),每一菜單中的選項(xiàng)都是一個(gè)菜單項(xiàng)(MenuItem)類(lèi)的實(shí)例。一個(gè)Application類(lèi)創(chuàng)建這些菜單和它們的菜單項(xiàng)以及其余的用戶(hù)界面。該Application類(lèi)還跟蹤用戶(hù)已打開(kāi)的Document對(duì)象。

該應(yīng)用為每一個(gè)菜單項(xiàng)配置一個(gè)具體的Command子類(lèi)的實(shí)例。當(dāng)用戶(hù)選擇了一個(gè)菜單項(xiàng)
時(shí),該 MenuItem對(duì)象調(diào)用它的Command對(duì)象的Execute方法,而Execute執(zhí)行相應(yīng)操作。
MenuItem對(duì)象并不知道它們使用的是Command的哪一個(gè)子類(lèi)。Command子類(lèi)里存放著請(qǐng)求的接收者,而Execute操作將調(diào)用該接收者的一個(gè)或多個(gè)操作。

例如,PasteCommand支持從剪貼板向一個(gè)文檔 ( Document )粘貼正文。PasteCommand的接收者是一個(gè)文檔對(duì)象,該對(duì)象是實(shí)例化時(shí)提供的。Execute操作將調(diào)用該 Document的Paste操作。


image.png

而OpenCommand的Execute操作卻有所不同:它提示用戶(hù)輸入一個(gè)文檔名,創(chuàng)建一個(gè)相應(yīng)的文檔對(duì)象,將其作為接受者的應(yīng)用對(duì)象中,并打開(kāi)文檔。


image.png

有時(shí)一個(gè)MenuItem需要執(zhí)行一系列命令。例如,使一個(gè)頁(yè)面按正常大小居中的MenuItem可由一個(gè)CenterDocumentCommand對(duì)象和一個(gè)NormalSizeCommand對(duì)象構(gòu)建。因?yàn)檫@種需將多條命令串接起來(lái)的情況很常見(jiàn)。我們定義一個(gè) MacroCommand類(lèi)來(lái)讓一個(gè)MenuItem執(zhí)行任意數(shù)目的命令。 MacroCommand 是一個(gè)具體的Command 子類(lèi),它執(zhí)行一個(gè)命令序列。MacroCommand沒(méi)有明確的接收者,而序列中的命令各自定義其接收者。
image.png

請(qǐng)注意這些例子中Command模式是怎樣解耦調(diào)用操作的對(duì)象和具有執(zhí)行該操作所需信息的那個(gè)對(duì)象的。這使我們?cè)谠O(shè)計(jì)用戶(hù)界面時(shí)擁有很大的靈活性。一個(gè)應(yīng)用如果想讓一個(gè)菜單與一個(gè)按鈕代表同一項(xiàng)功能,只需讓它們共享相應(yīng)具體Command子類(lèi)的同一個(gè)實(shí)例即可。我們還可以動(dòng)態(tài)地替換Command對(duì)象,這可用于實(shí)現(xiàn)上下文有關(guān)的菜單。我們也可通過(guò)將幾個(gè)命令組成更大的命令的形式來(lái)支持命令腳本 (command scripting)。所有這些之所以成為可能乃是因?yàn)樘峤灰粋€(gè)請(qǐng)求的對(duì)象僅需知道如何提交它,而不需知道該請(qǐng)求將會(huì)被如何執(zhí)行。

4 適用性

當(dāng)你有如下需求時(shí),可使用 Command模式:

  • 1 像上面討論的MenuItem對(duì)象那樣,抽象出待執(zhí)行的動(dòng)作以參數(shù)化某對(duì)象。你可用過(guò)程語(yǔ)言中的回調(diào)(callback)函數(shù)表達(dá)這種參數(shù)化機(jī)制。所謂回調(diào)函數(shù)是指函數(shù)先在某處注冊(cè),而它將在稍后某個(gè)需要的時(shí)候被調(diào)用。Command模式是回調(diào)機(jī)制的一個(gè)面向?qū)ο蟮奶娲贰?/p>

  • 2 在不同的時(shí)刻指定、排列和執(zhí)行請(qǐng)求。一個(gè)Command對(duì)象可以有一個(gè)與初始請(qǐng)求無(wú)關(guān)的生存期。如果一個(gè)請(qǐng)求的接受者可用一種與地址空間無(wú)關(guān)的方式表達(dá)。那么就可以將負(fù)責(zé)該請(qǐng)求的命令對(duì)象傳送給另一個(gè)不同的進(jìn)程并在那兒實(shí)現(xiàn)該請(qǐng)求。

  • 3 支持取消操作。Command的Excute操作可在實(shí)施操作前將狀態(tài)存儲(chǔ)起來(lái),在取消操作時(shí)這個(gè)狀態(tài)用來(lái)消除該操作的影響。Command接口必須添加一個(gè) UnExecute操作,該操作取消上一次Execute調(diào)用的效果。執(zhí)行的命令被存儲(chǔ)在一個(gè)歷史列表中。可通過(guò)向后和向前遍歷這一列表并分別調(diào)用UnExcute和Execute來(lái)實(shí)現(xiàn)重?cái)?shù)不限的“取消”和“重做”。

  • 4 支持修改日志,這樣當(dāng)系統(tǒng)崩潰時(shí),這些修改可以被重做一遍。在Command接口中添加裝載操作和存儲(chǔ)操作,可以用來(lái)保持變動(dòng)的一個(gè)一致的修改日志。從崩潰中恢復(fù)的過(guò)程包括從磁盤(pán)中重新讀入記錄下來(lái)的命令并用Execute操作重新執(zhí)行它們。

  • 5 用構(gòu)建在原語(yǔ)操作上的高層操作構(gòu)造一個(gè)系統(tǒng)。這樣一種結(jié)構(gòu)在支持事務(wù)( transaction )的信息系統(tǒng)中很常見(jiàn)。一個(gè)事務(wù)封裝了對(duì)數(shù)據(jù)的一組變動(dòng)。Command模式提供了對(duì)事務(wù)進(jìn)行建模的方法。Command有一個(gè)公共的接口,使得你可以用同一種方式調(diào)用所有的事務(wù)。同時(shí)使用該模式也易于添加新事務(wù)以擴(kuò)展系統(tǒng)。

5 結(jié)構(gòu)
image.png
6 參與者
  • Command
    ——聲明執(zhí)行操作的接口
  • ConcreteCommand(PasteCommand,OpenCommand)
    ——將一個(gè)接收者對(duì)象綁定于一個(gè)動(dòng)作
    ——調(diào)用接收者相應(yīng)的操作,以實(shí)現(xiàn)Execute
  • Client(Application)
    ——?jiǎng)?chuàng)建一個(gè)具體命令對(duì)象并設(shè)定它的接收者。
  • Invoke(MenuItem)
    ——要求該命令執(zhí)行這個(gè)請(qǐng)求。
  • Receiver(Document,Application)
    ——知道如何實(shí)施與執(zhí)行一個(gè)請(qǐng)求相關(guān)的操作,任何類(lèi)都可能作為一個(gè)接收者;
7 協(xié)作
  • Client創(chuàng)建一個(gè)ConcreteCommand對(duì)象并指定它的Receiver對(duì)象;
  • 某Invoker對(duì)象存儲(chǔ)該ConcreteCommand對(duì)象;
  • 該Invoker通過(guò)調(diào)用Command對(duì)象的Execute操作來(lái)提交一個(gè)請(qǐng)求。若該命令是可撤消的,ConcreteCommand就在執(zhí)行Execute操作之前存儲(chǔ)當(dāng)前狀態(tài)以用于取消該命令。
  • ConcreteCommand對(duì)象對(duì)調(diào)用它的R e c e i v e r的一些操作以執(zhí)行該請(qǐng)求。


    image.png

    下圖展示了這些對(duì)象之間的交互。它說(shuō)明了Command是如何將調(diào)用者和接收者 (以及它執(zhí)
    行的請(qǐng)求)解耦的。

8 效果

Command模式有以下效果:

  • 1 Command模式將調(diào)用操作的對(duì)象與知道如何實(shí)現(xiàn)該操作的對(duì)象解耦;
  • 2 Command是頭等的對(duì)象。它們可像其他的對(duì)象一樣被操縱和擴(kuò)展;
  • 3 你可將多個(gè)命令裝配成一個(gè)復(fù)合命令。例如是前面描述的MacroCommand類(lèi)。一般說(shuō)來(lái),復(fù)合命令是Composite模式的一個(gè)實(shí)例;
  • 4 增加新的Command很容易,因?yàn)檫@無(wú)需改變已有的類(lèi);
9 實(shí)現(xiàn)

實(shí)現(xiàn)Command模式時(shí)考慮以下問(wèn)題:

  • 1 一個(gè)命令對(duì)象應(yīng)達(dá)到何種智能程度 命令對(duì)象的能力可大可小。一個(gè)極端是它僅確定
    一個(gè)接收者和執(zhí)行該請(qǐng)求的動(dòng)作。另一極端是它自己實(shí)現(xiàn)所有功能,根本不需要額外的接收
    者對(duì)象。當(dāng)需要定義與已有的類(lèi)無(wú)關(guān)的命令,當(dāng)沒(méi)有合適的接收者,或當(dāng)一個(gè)命令隱式地知
    道它的接收者時(shí),可以使用后一極端方式。例如,創(chuàng)建另一個(gè)應(yīng)用窗口的命令對(duì)象本身可能
    和任何其他的對(duì)象一樣有能力創(chuàng)建該窗口。在這兩個(gè)極端間的情況是命令對(duì)象有足夠的信息
    可以動(dòng)態(tài)的找到它們的接收者。

  • 2 支持取消和重做 如果Command提供方法逆轉(zhuǎn)( reverse )它們操作的執(zhí)
    行 ( 例如Unexecute 或Undo操作 ) ,就可支持取消和重做功能。為達(dá)到這個(gè)目的,
    ConcreteCommand類(lèi)可能需要存儲(chǔ)額外的狀態(tài)信息。這個(gè)狀態(tài)包括:

  • 接收者對(duì)象,它真正執(zhí)行處理該請(qǐng)求的各操作;

  • 接收者上執(zhí)行操作的參數(shù);

  • 如果處理請(qǐng)求的操作會(huì)改變接收者對(duì)象中的某些值,那么這些值也必須先存儲(chǔ)起來(lái)。接收者還必須提供一些操作,以使該命令可將接收者恢復(fù)到它先前的狀態(tài)。

若應(yīng)用只支持一次取消操作,那么只需存儲(chǔ)最近一次被執(zhí)行的命令。而若要支持多級(jí)的
取消和重做,就需要有一個(gè)已被執(zhí)行命令的歷史表列 (history list),該表列的最大長(zhǎng)度決定了取消和重做的級(jí)數(shù)。歷史表列存儲(chǔ)了已被執(zhí)行的命令序列。向后遍歷該表列并逆向執(zhí)行( reverse - executing )命令是取消它們的結(jié)果;向前遍歷并執(zhí)行命令是重執(zhí)行它們。

有時(shí)可能不得不將一個(gè)可撤消的命令在它可以被放入歷史列表中之前先拷貝下來(lái)。這是因?yàn)閳?zhí)行原來(lái)的請(qǐng)求的命令對(duì)象將在稍后執(zhí)行其他的請(qǐng)求。如果命令的狀態(tài)在各次調(diào)用之間會(huì)發(fā)生變化,那就必須進(jìn)行拷貝以區(qū)分相同命令的不同調(diào)用。

例如,一個(gè)刪除選定對(duì)象的刪除命令 ( DeleteCommand )在它每次被執(zhí)行時(shí),必須存儲(chǔ)不同的對(duì)象集合。因此該刪除命令對(duì)象在執(zhí)行后必須被拷貝,并且將該拷貝放入歷史表列中。如果該命令的狀態(tài)在執(zhí)行時(shí)從不改變,則不需要拷貝,而僅需將一個(gè)對(duì)該命令的引用放入歷史表列中。在放入歷史表列中之前必須被拷貝的那些 Command起著原型(參見(jiàn) Prototype模式(3 . 4))的作用。

  • 3 避免取消操作過(guò)程中的錯(cuò)誤積累 在實(shí)現(xiàn)一個(gè)可靠的、能保持原先語(yǔ)義的取消 /重做機(jī)
    制時(shí),可能會(huì)遇到滯后影響問(wèn)題。由于命令重復(fù)的執(zhí)行、取消執(zhí)行,和重執(zhí)行的過(guò)程可能會(huì)
    積累錯(cuò)誤,以至一個(gè)應(yīng)用的狀態(tài)最終偏離初始值。這就有必要在Command中存入更多的信息以保證這些對(duì)象可被精確地復(fù)原成它們的初始狀態(tài)。這里可使用 Memento模式(5 . 6)來(lái)讓該Command訪(fǎng)問(wèn)這些信息而不暴露其他對(duì)象的內(nèi)部信息。

  • 4 使用C + +模板 對(duì)( 1 )不能被取消 ( 2 )不需要參數(shù)的命令,我們可使用 C + +模板來(lái)實(shí)現(xiàn),這樣可以避免為每一種動(dòng)作和接收者都創(chuàng)建一個(gè)Command子類(lèi)。我們將在代碼示例一節(jié)說(shuō)明這種做法。

10 代碼示例

github地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,415評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,104評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,884評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,647評(píng)論 6 408
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,130評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,366評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,887評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,737評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,939評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,174評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,586評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,827評(píng)論 1 283
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,608評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,914評(píng)論 2 372