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)求所需的具體信息。
用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操作。
而OpenCommand的Execute操作卻有所不同:它提示用戶(hù)輸入一個(gè)文檔名,創(chuàng)建一個(gè)相應(yīng)的文檔對(duì)象,將其作為接受者的應(yīng)用對(duì)象中,并打開(kāi)文檔。
有時(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)有明確的接收者,而序列中的命令各自定義其接收者。
請(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)
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ō)明這種做法。