當我們到了一間餐廳,我們在點餐的時候,服務生會把餐點的內容記在點餐單上面,這張點餐單會跟著其他客人的點餐單一起送入廚房的待作列表上。然後廚師會依據先到先做的原則,把點餐單上的內容,把餐點一個個做出來,然後等很久的客人也可以選擇不等了。
咦?不是在說設計模式嗎? 是的,沒看錯,已經把命令模式的架構講完了
點餐單就是命令,待作列表就是命令管理器,廚師是功能提供者,把命令一個一個依照順序製作(執行)出來,最後客人也可以選擇取消命令
定義:將請求封裝成物件,讓你可以將客戶端的不同請求參數化,並配合佇列、記錄、復原等方法來操作請求
簡單分兩部分來看
- 請求的封裝
- 請求的操作
請求的封裝:某客戶元件,想要呼叫執行某樣功能是被時作在某類別中。一般最直接的作法就是透過直接呼叫該類別物件的方式。但有時呼叫一個功能的請求需要傳入許多參數,當功能執行端提供過多的參數讓客戶端選擇時,就會發生參數列過多的情況。為了方便閱讀通常會建議將這些參數列上的設定,以一個類別加以封裝,將呼叫功能時所需的參數加以封裝,如同把客人點餐的內容寫在點餐單上。如果將「封裝」的動作在進一步的話,也就是連同呼叫「功能執行端」也一起被封裝到類別中。幾種發生時機:功能執行端(類別)不確定有多個選擇之下或客戶端是一個通用元件不想與特定實作綁在一起
請求的操作:當請求可以被封裝成一個物件時,那麼這個請求就可以被操作:
- 儲存:可以將「請求物件」放入一個「資料結構」中進行排序、排序、排對、移除、暫緩執行...
- 記錄:當某一個 請求物件被執行之後,可以先不刪除,將其移入「已執行」資料容器內。透過檢視「已執行」資料容器的內容,就可以知道系統過去執行命令的流程及軌跡
- 復原:延續上一項紀錄,若系統真對每一項請求命令實行了「反向」操作時,可以將以執行的請求復原
直接來看程式吧:
Command(命令介面): 點餐單介面
定義命令封裝後要具備的操作介面
ConcreteCommand(命令實作):點餐單實作,飲料或餐點的menu
實作命令封裝及介面,會包含每一個命令的參數及Receiver(功能實作者)
Receiver(功能實作者):廚師
被封裝在ConcreteCommand(命令實作)類別中,真正執行功能的類別物件
Client(客戶端/命令發起者):客人
產生命令的客戶端,可以視情況設定命令給Receiver(功能執行者)
Invoker(命令管理者):待作列表
命令物件的管理容器,或是管理類別,並負責要求每個Command(命令)執行其功能
Receiver類別
兩個功能類別
Command介面
定義Excute方法讓Invoker(命令管理者)可以要求Receiver(功能實作者)執行命令
ConcreteCommand實作
因為有兩個功能執行類別,所以分別實作兩個命令子類別來封裝他們
將命令和Receiver物件繫結起來
Invoker類別
用List泛型容器來暫存命令物件。並於執行命令方法(ExcuteCommad)被呼叫時,才一次執行所有命令,並清空所有已經被執行的命令
Client(客戶端)
測試程式就是Client(客戶端),用來產生命令並加入Invoker(命令管理者)
執行結果:
Receiver1.Action:Command[你好]
Receiver2.Action:Param[999]
命令模式在實作上的彈性非常大,所以有許多變化的形式。
在實作分析上,可以著重在「命令物件」的「操作行為」來加以分析
- 如果想要讓「命令物件」能包含最多可能的執行方法數量,那麼就加強在命令類別群組的設計分析。以餐廳點餐為例,就是要思考,是否要將餐點與飲料的點餐單合併為一張
- 如果希望能讓命令可以任意的執行及返回,那麼就要著重於命令管理者的設計實作上。以餐廳點餐為例,就是要思考,點餐單的管理是要由人工管理還是電腦系統輔助
- 如果要讓命令具備任意返回或不執行的功能,那系統對命令的「反向操作」的定義也需要加以實作,或是將反向思考的執行參數,也一併封裝在命令類別中
命令模式的存在主要是為了管理
當請求被物件化後,對於請求物件是否有「管理」上的需求,如果有,則以命令模式實作
當需要實作大量的請求命令時,若每一個請求命令都需要產生類別的話,那麼會產生「類別過多」,為了避免這樣的問題產生,可以改用下列方式實作
- 使用「註冊回呼函式」:一樣所有命令由管理容器組織起來,並將功能執行者改為一個函式,而非類別物件。最後,將多個相同功能的回呼函式以一個類別封裝在一起
命令介面於實作類別,功能執行者為回呼類別之函式
回呼函式類別
命令管理者類別
- 使用泛型程式設計:將命令介面以泛型方式來設計,將功能執行者定義為泛型類別,命令執行時呼叫泛型類別的「固定方法」
這裡用第一個範例來改
命令介面於實作類別,把功能實作更改為泛型
功能實作者定義為泛型
命令管理者類別沒改
最後測試程式碼
用泛型的限制:
- 必須限定每個命令可以封裝的參數數量,且封裝的參數名稱比較不直覺,像這裡只用t來命名
- 因為固定呼叫「功能執行者」中的某一個方法,所以方法名稱固定,比較不容易與實際功能聯想
但如果系統中的每個命令都很單純時,使用泛型可以省去重複定義類別或回呼函式的麻煩
參考《設計模式與遊戲開發的完美結合》的DesignPattern-Command命令模式
參考網址:http://www.manew.com/thread-97626-1-1.html
留言列表