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

1 意圖

將一個請求封裝為一個對象近弟,從而使你可用不同的請求對客戶進行參數(shù)化祝峻;對請求排隊或記錄請求日志,以及支持可撤消的操作堵漱。

2 別名

動作( Action )综慎,事務( Transaction )

3 動機

有時必須向某對象提交請求,但并不知道關于被請求的操作或請求的接受者的任何信息勤庐。例如示惊,用戶界面工具箱包括按鈕和菜單這樣的對象,它們執(zhí)行請求響應用戶輸入埃元。但工具箱涝涤,不能顯式的在按鈕或菜單實現(xiàn)該請求,因為只有使用工具箱的應用知道該由哪個對象做哪個操作岛杀。而工具箱的設計者無法知道請求的接受者或執(zhí)行的操作。

命令模式通過將請求本身變成一個對象使工具箱對象可向未指定的應用對象提出請求崭孤。這個對象可被存儲并像其他的對象一樣被傳遞类嗤。這一模式的關鍵是一個抽象的Command類,它定義了一個執(zhí)行操作的接口辨宠。其最簡單的形式是一個抽象的Excute操作遗锣。具體的Command子類將接受者作為其一個實例變量,并實現(xiàn)Execute操作嗤形,指定接收站采取的動作精偿。而接收者有執(zhí)行該請求所需的具體信息。


image.png

用Command對象可以可很容易的實現(xiàn)菜單(Menu)赋兵,每一菜單中的選項都是一個菜單項(MenuItem)類的實例笔咽。一個Application類創(chuàng)建這些菜單和它們的菜單項以及其余的用戶界面。該Application類還跟蹤用戶已打開的Document對象霹期。

該應用為每一個菜單項配置一個具體的Command子類的實例叶组。當用戶選擇了一個菜單項
時,該 MenuItem對象調(diào)用它的Command對象的Execute方法历造,而Execute執(zhí)行相應操作甩十。
MenuItem對象并不知道它們使用的是Command的哪一個子類。Command子類里存放著請求的接收者勇哗,而Execute操作將調(diào)用該接收者的一個或多個操作颁督。

例如劈彪,PasteCommand支持從剪貼板向一個文檔 ( Document )粘貼正文。PasteCommand的接收者是一個文檔對象橄霉,該對象是實例化時提供的。Execute操作將調(diào)用該 Document的Paste操作荒典。


image.png

而OpenCommand的Execute操作卻有所不同:它提示用戶輸入一個文檔名酪劫,創(chuàng)建一個相應的文檔對象吞鸭,將其作為接受者的應用對象中,并打開文檔覆糟。


image.png

有時一個MenuItem需要執(zhí)行一系列命令刻剥。例如,使一個頁面按正常大小居中的MenuItem可由一個CenterDocumentCommand對象和一個NormalSizeCommand對象構(gòu)建滩字。因為這種需將多條命令串接起來的情況很常見造虏。我們定義一個 MacroCommand類來讓一個MenuItem執(zhí)行任意數(shù)目的命令。 MacroCommand 是一個具體的Command 子類麦箍,它執(zhí)行一個命令序列漓藕。MacroCommand沒有明確的接收者,而序列中的命令各自定義其接收者挟裂。
image.png

請注意這些例子中Command模式是怎樣解耦調(diào)用操作的對象和具有執(zhí)行該操作所需信息的那個對象的享钞。這使我們在設計用戶界面時擁有很大的靈活性。一個應用如果想讓一個菜單與一個按鈕代表同一項功能诀蓉,只需讓它們共享相應具體Command子類的同一個實例即可栗竖。我們還可以動態(tài)地替換Command對象,這可用于實現(xiàn)上下文有關的菜單渠啤。我們也可通過將幾個命令組成更大的命令的形式來支持命令腳本 (command scripting)狐肢。所有這些之所以成為可能乃是因為提交一個請求的對象僅需知道如何提交它,而不需知道該請求將會被如何執(zhí)行沥曹。

4 適用性

當你有如下需求時份名,可使用 Command模式:

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

  • 2 在不同的時刻指定想邦、排列和執(zhí)行請求。一個Command對象可以有一個與初始請求無關的生存期委刘。如果一個請求的接受者可用一種與地址空間無關的方式表達丧没。那么就可以將負責該請求的命令對象傳送給另一個不同的進程并在那兒實現(xiàn)該請求。

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

  • 4 支持修改日志擂找,這樣當系統(tǒng)崩潰時,這些修改可以被重做一遍浩销。在Command接口中添加裝載操作和存儲操作贯涎,可以用來保持變動的一個一致的修改日志。從崩潰中恢復的過程包括從磁盤中重新讀入記錄下來的命令并用Execute操作重新執(zhí)行它們慢洋。

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

5 結(jié)構(gòu)
image.png
6 參與者
  • Command
    ——聲明執(zhí)行操作的接口
  • ConcreteCommand(PasteCommand,OpenCommand)
    ——將一個接收者對象綁定于一個動作
    ——調(diào)用接收者相應的操作蜒车,以實現(xiàn)Execute
  • Client(Application)
    ——創(chuàng)建一個具體命令對象并設定它的接收者盈包。
  • Invoke(MenuItem)
    ——要求該命令執(zhí)行這個請求。
  • Receiver(Document醇王,Application)
    ——知道如何實施與執(zhí)行一個請求相關的操作,任何類都可能作為一個接收者崭添;
7 協(xié)作
  • Client創(chuàng)建一個ConcreteCommand對象并指定它的Receiver對象寓娩;
  • 某Invoker對象存儲該ConcreteCommand對象;
  • 該Invoker通過調(diào)用Command對象的Execute操作來提交一個請求呼渣。若該命令是可撤消的棘伴,ConcreteCommand就在執(zhí)行Execute操作之前存儲當前狀態(tài)以用于取消該命令。
  • ConcreteCommand對象對調(diào)用它的R e c e i v e r的一些操作以執(zhí)行該請求屁置。


    image.png

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

8 效果

Command模式有以下效果:

  • 1 Command模式將調(diào)用操作的對象與知道如何實現(xiàn)該操作的對象解耦蓝角;
  • 2 Command是頭等的對象阱穗。它們可像其他的對象一樣被操縱和擴展;
  • 3 你可將多個命令裝配成一個復合命令使鹅。例如是前面描述的MacroCommand類揪阶。一般說來,復合命令是Composite模式的一個實例患朱;
  • 4 增加新的Command很容易鲁僚,因為這無需改變已有的類;
9 實現(xiàn)

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

  • 1 一個命令對象應達到何種智能程度 命令對象的能力可大可小。一個極端是它僅確定
    一個接收者和執(zhí)行該請求的動作冰沙。另一極端是它自己實現(xiàn)所有功能侨艾,根本不需要額外的接收
    者對象。當需要定義與已有的類無關的命令拓挥,當沒有合適的接收者唠梨,或當一個命令隱式地知
    道它的接收者時,可以使用后一極端方式撞叽。例如姻成,創(chuàng)建另一個應用窗口的命令對象本身可能
    和任何其他的對象一樣有能力創(chuàng)建該窗口。在這兩個極端間的情況是命令對象有足夠的信息
    可以動態(tài)的找到它們的接收者愿棋。

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

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

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

  • 如果處理請求的操作會改變接收者對象中的某些值甘邀,那么這些值也必須先存儲起來琅攘。接收者還必須提供一些操作,以使該命令可將接收者恢復到它先前的狀態(tài)松邪。

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

有時可能不得不將一個可撤消的命令在它可以被放入歷史列表中之前先拷貝下來褂傀。這是因為執(zhí)行原來的請求的命令對象將在稍后執(zhí)行其他的請求忍啤。如果命令的狀態(tài)在各次調(diào)用之間會發(fā)生變化,那就必須進行拷貝以區(qū)分相同命令的不同調(diào)用仙辟。

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

  • 3 避免取消操作過程中的錯誤積累 在實現(xiàn)一個可靠的筒溃、能保持原先語義的取消 /重做機
    制時马篮,可能會遇到滯后影響問題。由于命令重復的執(zhí)行怜奖、取消執(zhí)行浑测,和重執(zhí)行的過程可能會
    積累錯誤,以至一個應用的狀態(tài)最終偏離初始值歪玲。這就有必要在Command中存入更多的信息以保證這些對象可被精確地復原成它們的初始狀態(tài)迁央。這里可使用 Memento模式(5 . 6)來讓該Command訪問這些信息而不暴露其他對象的內(nèi)部信息。

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

10 代碼示例

github地址

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蜂科,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子短条,更是在濱河造成了極大的恐慌导匣,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茸时,死亡現(xiàn)場離奇詭異贡定,居然都是意外死亡,警方通過查閱死者的電腦和手機可都,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進店門厕氨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人汹粤,你說我怎么就攤上這事√锿恚” “怎么了嘱兼?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長贤徒。 經(jīng)常有香客問我芹壕,道長,這世上最難降的妖魔是什么接奈? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任踢涌,我火速辦了婚禮,結(jié)果婚禮上序宦,老公的妹妹穿的比我還像新娘睁壁。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布潘明。 她就那樣靜靜地躺著行剂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪钳降。 梳的紋絲不亂的頭發(fā)上厚宰,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天,我揣著相機與錄音遂填,去河邊找鬼铲觉。 笑死,一個胖子當著我的面吹牛吓坚,可吹牛的內(nèi)容都是我干的撵幽。 我是一名探鬼主播,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼凌唬,長吁一口氣:“原來是場噩夢啊……” “哼并齐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起客税,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤况褪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后更耻,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體测垛,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年秧均,在試婚紗的時候發(fā)現(xiàn)自己被綠了食侮。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡目胡,死狀恐怖锯七,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情誉己,我是刑警寧澤眉尸,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站巨双,受9級特大地震影響噪猾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜筑累,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一袱蜡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧慢宗,春花似錦坪蚁、人聲如沸奔穿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽巫橄。三九已至,卻和暖如春茵典,著一層夾襖步出監(jiān)牢的瞬間湘换,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工统阿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留彩倚,地道東北人。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓扶平,卻偏偏與公主長得像帆离,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子结澄,可洞房花燭夜當晚...
    茶點故事閱讀 45,870評論 2 361