天天看點

COMMAND 指令模式 --對象行為型模式

1、意圖

        将一個請求封裝為一個對象,進而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日志,以及支援可撤銷的操作。

2、别名

        動作(Action)模式    事務(Transaction)模式

3、動機

        有時必須向某對象送出請求,但并不知道關于被請求的操作或請求的接受者的任何資訊。例如,使用者界面工具箱包括按鈕和菜單這樣的對象,他們執行請求響應使用者輸入。但工具箱不能顯式地在按鈕或菜單中實作該請求,因為隻有使用工具箱的應用知道該由哪個對象做哪個操作。而工具箱的設計者無法知道請求的接受者或執行的操作。

        指令模式通過将請求本身變成一個對象來使工具箱對象可向未指定的應用對象提出請求。這個對象可被存儲并像其他的對象一樣被傳遞。這一模式的關鍵是一個抽象的Command類,它定義了一個執行操作的接口。其最簡單的形式是一個抽象的Exccute操作。具體的Command子類将接受者作為其一個執行個體變量,并實作Execute操作,指定接受者采取的動作。而接收者有執行該請求所需的具體資訊。

COMMAND 指令模式 --對象行為型模式

        用Command對象可很容易的實作菜單(Menu),每一菜單中的選項都是一個菜單項(MenuItem)類的執行個體。一個Application類穿件這些菜單和它們的菜單項以及其餘的使用者界面。該Application類還跟蹤使用者已打開的Document對象。

        該應用為每一個菜單項配置一個具體的Comman子類的執行個體。當使用者選擇了一個菜單項時,該MenuItem對象調用它的Command對象的Execute方法,而Execute執行相應操作。MenuItem對象并不知道它們使用的是Command的哪一個子類。Command子類裡存放着請求的接收者,而Execute操作将調用該接收者的一個或多個操作。

        例如,PasteCommand支援從剪貼闆向一個文檔(Document)粘貼正文。PasteCommand的接收者是一個文檔對象,該對象是執行個體化時提供的。Execute操作将調用該Document的Paste操作。

COMMAND 指令模式 --對象行為型模式

        而OpenCommand的Execute操作卻有所不同:它提示使用者輸入一個文檔名,建立一個相應的文檔對象,将其入作為接收者的應用對象中,并打開該文檔。

COMMAND 指令模式 --對象行為型模式

        有時一個MenuItem需要執行一系列指令。例如,使一個頁面按正常大小居中的MenuItem可由一個CenterDocumentCommand對象和一個NormalSizeCommand對象建構。因為這種需将多條指令串接起來的情況很常見,我們定義一個MacroCommand類來讓一個MenuItem執行任意數目的指令。MacroCommand是一個具體的Command子類,它執行一個指令序列。MacroCommand沒有明确的接收者,而序列中的指令各自定義其接收者。

COMMAND 指令模式 --對象行為型模式

       請注意這些例子中Command模式是怎樣解耦調用操作的對象和具有執行該操作所需資訊的那個對象的。這使我們在設計使用者界面時擁有很大的靈活性。一個應用如果想讓一個菜單與一個按鈕代表同一項功能,隻需讓他們共享相應具體Command子類的同一個執行個體即可。我們還可以動态地替換Command對象,者可用于實作上下文有關的菜單。我們也可通過将幾個指令組成更大的指令的形式來支援指令腳本(command scripting)。所有這些之是以成為可能乃是因為送出一個請求的對象僅需知道如何送出它,而不需要知道該請求将會被如何執行。

4、适用性

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

        · 像上面讨論的MenuItem對象那樣,抽象出待執行的動作以參數化某對象。你可用過程語言中的回調函數表達這種參數化機制。所謂回調函數是指函數先在某處注冊,而它将在售後某個需要的時候被調用。Command模式是回調機制的一個面向對象的替代品。

        · 在不同的時刻指定、排列和執行請求。一個Command對象可以有一個與初始請求無關的生存期。如果一個請求的接收者可用一種與位址空間無關的方式表達,那麼就可将負責該請求的指令對象傳送給另一個不同的程序并在那兒實作該請求。

        · 支援取消操作。Command的Execute操作可在實施操作前将狀态存儲起來,在取消操作時這個狀态用來消除該操作的影響。Command接口必須添加一個Unexecute操作,該操作取消上一次Execute調用的效果。執行的指令被存儲在一個曆史清單中。可通過向後和向前周遊這一清單并分别調用Unexecute和Execute來實作重數不限的“取消”和“重做”。

        · 支援修改日志,這樣當系統崩潰時,這些修改可以被重做一遍。在Command接口中添加裝在操作和存儲操作,可以用來保持變動的一個一緻的修改日志。從崩潰中回複的過程包括從磁盤中重新讀入記錄下來的指令并用Execute操作重新執行它們。

        · 用建構在原語操作上的高層操作構造一個系統。這樣一種結構在支援事務(transaction)的資訊系統中很常見。一個事務封裝了對資料的一組變動。Command模式提供了對事務進行模組化的方法。Command有一個公共的接口,使得你可以用同一種方式調用所有的事務。同時使用該模式也易于添加新事務以擴充系統。

5、結構

COMMAND 指令模式 --對象行為型模式

6、參與者

        · Command

          --聲明執行操作的接口。

        · ConcreteCommand(PasteCommand,OpenCommand)

          -- 将一個接收者對象綁定于一個動作。

          -- 調用接收者相應的操作,以實作Execute。

        · Client(Application)

          -- 建立一個具體指令對象并設定它的接收者。

        · Invoker(MenuItem)

          -- 要求該指令執行這個請求。

        · Receiver(Document,Application)

          -- 知道如何實施與執行一個請求相關的操作。任何類都可能作為一個接收者。

7、協作

        · Client建立一個ConcreteCommand對象并指定它的Receiver對象。

        · 某Invoker對象存儲該ConcreteCommand對象。

        · 該Invoker通過調用Command對象的Execute操作來送出一個請求。若該指令是可撤銷的,ConcreteCommand就在執行Execute操作之前存儲目前狀态以用于取消該指令。

        · ConcreteCommand對象調用它的Receiver的一些操作以執行該請求。    

COMMAND 指令模式 --對象行為型模式

        上圖展示了這些對象之間的互動。它說明了Command是如何将調用者和接收者(以及它執行的請求)解耦的。

8、效果

        Command模式有以下效果:

        1)Command模式将調用操作的對象與知道如何實作該操作的對象解耦。

        2)Command是頭等的對象。它們可像其他的對象一樣被操縱和擴充。

        3)你可将多個指令裝配成一個複合指令。例如是前面描述的MacroCommand類。一般說來,複合指令是Composite模式的一個執行個體。

        4)增加新的Command很容易,因為這無需改變已有的類。

9、實作

        實作Command模式時須考慮以下問題:

        1)一個指令對象應達到何種智能程度    指令對象的能力可大可小。一個極端是它僅确定一個接收者和執行該請求的動作。另一個極端是它自己實作所有的功能,根本不需要額外的接收者對象。當需要定義與已有的類無關的指令,當沒有合适的接收者,或當一個指令隐式的知道它的接收者時,可以使用後一極端方式。例如,建立另一個應用視窗的指令對象本身可能和任何其他的對象一樣有能力建立該視窗。在這兩個極端間的情況是指令對象有足夠的資訊可以動态的找到他們的接收者。

        2)支援取消(undo)和重做(redo)    如果Command提供方法逆轉(reverse)它們操作的執行(例如Unexecute或Undo操作),就可以支援取消和重做功能。為達到這個目的,ConcreteCommand類可能需要存儲額外的狀态資訊。這個狀态包括:

          · 接收者對象,它真正執行處理該請求的各操作。

          · 接收者上執行操作的參數。

          · 如果處理請求的操作會改變接收者對象中的某些值,那麼這些值也必須先存儲起來。接收者還必須提供一些操作,以使該指令可将接收者恢複到它先前的狀态。

        若應用隻支援一次取消操作,那麼隻需存儲最近一次被執行的指令。而若要支援多級的取消和重做,就需要有一個已被執行指令的曆史表列(history list),該表列的最大長度決定了取消和重做的級數。曆史表列存儲了已被執行的指令序列。向後周遊該表列并逆向執行(reverse-executing)指令是取消他們的結果;向前周遊并執行指令是重執行它們。

        有時可能不得不将一個可撤銷的指令在它可以被放入曆史清單中之前先拷貝下來。這是因為執行原來的請求的指令對象将在稍後執行其他的請求。如果指令的狀态在各次調用之間會發生變化,那就必須進行拷貝以區分相同指令的不同調用。

         例如,一個删除標明對象的删除指令(DelteCommand)在它每次被執行時,必須存儲不同的對象集合。是以該删除指令對象在執行後必須被拷貝,并且将該拷貝放入曆史表列中。如果該指令的狀态在執行時從不改變,則不需要拷貝,而僅需将一個對該指令的引用放入曆史表列中。在放入曆史表列中之前必須被拷貝的哪些Command起着原型的作用。

        3)避免取消操作過程中的錯誤積累     在實作一個可靠的、能保持原先語義的取消/重做機制時,可能會遇到之後影響問題。猶豫指令重複的執行、取消執行,和重執行的過程可能會積累錯誤,以至一個應用的狀态最終偏離初始值。這就有必要在Command中存入更多的資訊以保證這些對象可被精确地複原成它們的初始狀态。這裡可使用Memento模式來讓該Command通路這些資訊而不暴露其他對象的内部資訊。

        4)使用C++模闆    對(1)不能被取消(2)不需要參數的指令,我們可使用C++模闆來實作,這樣可以避免為每一種動作和接收者都建立一個Command子類。我們将在代碼示例一節說明這種做法。

10、代碼示例

        。。。。。。

繼續閱讀