17.命令模式Command

1.初識命令模式

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

  • Command:
    定義命令的接口,聲明執行的方法。
  • ConcreteCommand:
    命令接口實現對象,是“虛”的實現;通常會持有接收者,并調用接收者的功能來完成命令要執行的操作。
  • Receiver:
    接收者,真正執行命令的對象。任何類都可能成為一個接收者,只要它能夠實現命令要求實現的相應功能。
  • Invoker:
    要求命令對象執行請求,通常會持有命令對象,可以持有很多的命令對象。這個是客戶端真正觸發命令并要求命令執行相應操作的地方,也就是說相當于使用命令對象的入口。
  • Client:
    創建具體的命令對象,并且設置命令對象的接收者。注意這個不是我們常規意義上的客戶端,而是在組裝命令對象和接收者,或許,把這個Client稱為裝配者會更好理解,因為真正使用命令的客戶端是從Invoker來觸發執行。

2.體會命令模式

2.1 場景問題——如何開機

當我們按下啟動按鈕過后呢?誰來處理?如何處理?都經歷了怎樣的過程,才讓電腦真正的啟動起來,供我們使用呢?
如果現在要求用軟件把開機的過程表現出來,該如何實現?

首先把開機的過程總結一下,主要就這么幾個步驟:首先加載電源,然后是設備檢查,再然后是裝載系統,最后電腦就正常啟動了。可是誰來完成這些過程?如何完成?

不能讓使用電腦的客戶——就是我們來做這些工作吧,真正完成這些工作的是主板,那么客戶和主板如何發生聯系呢?現實中,是用連接線把按鈕連接到主板上的,這樣當客戶按下按鈕的時候,就相當于發命令給主板,讓主板去完成后續的工作。

另外,從客戶的角度來看,開機就是按下按鈕,不管什么樣的主板都是一樣的,也就是說,客戶只管發出命令,誰接收命令,誰實現命令,如何實現,客戶是不關心的。

把上面的問題抽象描述一下:
客戶端只是想要發出命令或者請求,不關心請求的真正接收者是誰,也不關心具體如何實現,而且同一個請求的動作可以有不同的請求內容,當然具體的處理功能也不一樣,請問該怎么實現?

2.2 使用模式的解決方案


要用程序來解決上面提出的問題,一種自然的方案就是來模擬上述解決思路。

在命令模式中,會定義一個命令的接口,用來約束所有的命令對象,每個命令實現對象是對客戶端某個請求的封裝,對應于機箱上的按鈕,一個機箱上可以有很多按鈕,也就相當于會有多個具體的命令實現對象。

在命令模式中,命令對象并不知道如何處理命令,會有相應的接收者對象來真正執行命令。就像電腦的例子,機箱上的按鈕并不知道如何處理功能,而是把這個請求轉發給主板,由主板來執行真正的功能,這個主板就相當于命令模式的接收者。

在命令模式中,命令對象和接收者對象的關系,并不是與生俱來的,需要有一個裝配的過程,命令模式中的Client對象就來實現這樣的功能。這就相當于在電腦的例子中,有了機箱上的按鈕,也有了主板,還需要有一個連接線把這個按鈕連接到主板上才行。

命令模式還會提供一個Invoker對象來持有命令對象,就像電腦的例子,機箱上會有多個按鈕,這個機箱就相當于命令模式的Invoker對象。這樣一來,命令模式的客戶端就可以通過Invoker來觸發并要求執行相應的命令了,這也相當于真正的客戶是按下機箱上的按鈕來操作電腦一樣。

3.理解命令模式

3.1 認識命令模式

3.1.1 命令模式的關鍵

命令模式的關鍵之處就是把請求封裝成為對象,也就是命令對象,并定義了統一的執行操作的接口,這個命令對象可以被存儲、轉發、記錄、處理、撤銷等,整個命令模式都是圍繞這個對象在進行。

3.1.2 命令模式的組裝和調用

在命令模式中經常會有一個命令的組裝者,用它來維護命令的“虛”實現和真實實現之間的關系。如果是超級智能的命令,也就是說命令對象自己完全實現好了,不需要接收者,那就是命令模式的退化,不需要接收者,自然也不需要組裝者了。

而真正的用戶就是具體化請求的內容,然后提交請求進行觸發就好了。真正的用戶會通過invoker來觸發命令。

在實際開發中,Client和Invoker可以融合在一起,由客戶在使用命令模式時,先進行命令對象和接收者的組裝,組裝完成后,再調用命令執行請求。

3.1.3 命令模式的接收者

接收者可以是任意的類,對它沒有什么特殊要求,這個對象知道如何真正執行命令的操作,執行時是從command的實現類里面轉調過來。

一個接收者對象可以處理多個命令,接收者和命令之間沒有約定的對應關系。接收者提供的方法個數、名稱、功能和命令中的可以不一樣,只要能夠通過調用接收者的方法來實現命令對應的功能就可以了。

3.1.4 智能命令

在標準的命令模式里面,命令的實現類是沒有真正實現命令要求的功能的,真正執行命令的功能的是接收者。

如果命令的實現對象比較智能,它自己就能真實地實現命令要求的功能,而不再需要調用接收者,那么這種情況就稱為智能命令。

也可以有半智能的命令,命令對象知道部分實現,其它的還是需要調用接收者來完成,也就是說命令的功能由命令對象和接收者共同來完成。

3.1.5 發起請求的對象和真正實現的對象是解耦的

請求究竟由誰處理,如何處理,發起請求的對象是不知道的,也就是發起請求的對象和真正實現的對象是解耦的。發起請求的對象只管發出命令,其它的就不管了。

3.1.6 命令模式的調用順序示意圖

使用命令模式的過程分成兩個階段,一個階段是組裝命令對象和接收者對象的過程,另外一個階段是觸發調用Invoker,來讓命令真正執行的過程。


再看看真正執行命令時的調用順序示意圖:


3.2 參數化配置

所謂命令模式的參數化配置,指的是:可以用不同的命令對象,去參數化配置客戶的請求。

像前面描述的那樣:客戶按下一個按鈕,到底是開機還是重啟,那要看參數化配置的是哪一個具體的按鈕對象,如果參數化的是開機的命令對象,那就執行開機的功能,如果參數化的是重啟的命令對象,那就執行重啟的功能。雖然按下的是同一個按鈕,相當于是同一個請求,但是為請求配置不同的按鈕對象,那就會執行不同的功能。

3.3 可撤銷的操作

可撤銷操作的意思就是:放棄該操作,回到未執行該操作前的狀態。這是一個非常重要的功能,幾乎所有GUI應用里都有撤消操作的功能。GUI的菜單是命令模式最典型的應用之一,所以你總是能在菜單上找到撤銷這樣的菜單項。

既然這么常用,那該如何實現呢?

有兩種基本的思路來實現可撤銷的操作,一種是補償式,又稱反操作式:

  • 比如被撤銷的操作是加的功能,那撤消的實現就變成減的功能;同理被撤銷的操作是打開的功能,那么撤銷的實現就變成關閉的功能。
  • 另外一種方式是存儲恢復式,意思就是把操作前的狀態記錄下來,然后要撤銷操作的時候就直接恢復回去就可以了。

這里先講第一種方式,就是補償式或者反操作式,第二種方式放到備忘錄模式中去講解。

實例如下:
考慮一個計算器的功能,最簡單的那種,只能實現加減法運算,現在要讓這個計算器支持可撤銷的操作。

3.4 宏命令

簡單點說就是包含多個命令的命令,是一個命令的組合。
舉個例子來說吧,設想一下你去飯店吃飯的過程:


3.4.1 宏命令在哪里?

現實中是當你你點完菜,說“點完了”的時候,服務員才會啟動命令的執行,請注意,這個時候執行的就不是一個命令了,而是執行一堆命令。

描述這一堆命令的就是菜單,如果把菜單也抽象成為一個命令,就相當于一個大的命令,當客戶說“點完了”的時候,就相當于觸發這個大的命令,意思就是執行菜單這個命令就可以了,這個菜單命令包含多個命令對象,一個命令對象就相當于一道菜。那么這個菜單就相當于我們說的宏命令。

3.4.2 如何實現宏命令

宏命令從本質上講類似于一個命令,基本上把它當命令對象進行處理。但是它跟普通的命令對象又有些不一樣,就是宏命令包含有多個普通的命令對象,執行一個宏命令,簡單點說,就是執行宏命令里面所包含的所有命令對象,有點打包執行的意味。

3.5 隊列請求

所謂隊列請求,就是對命令對象進行排隊,組成工作隊列,然后依次取出命令對象來執行。多用多線程或者線程池來進行命令隊列的處理,當然也可以不用多線程,就是一個線程,一個命令一個命令的循環處理,就是慢點。

3.6 日志請求

所謂日志請求,就是把請求的歷史記錄保存下來,一般是采用永久存儲的方式。如果運行請求的過程中,系統崩潰了,那么在系統再次運行時,就可以從保存的歷史記錄里面獲取日志請求,并重新執行命令。

日志請求的實現有兩種方案:

  • 一種就是直接使用Java中的序列化方法;
  • 另外一種就是在命令對象里面添加上存儲和裝載的方法,其實就是讓命令對象自己實現類似序列化的功能。

當然要簡單就直接使用Java中的序列化。

3.7 命令模式的優缺點

  • 更松散的耦合
  • 更動態的控制
  • 能很自然的復合命令
  • 更好的擴展性

4.思考命令模式

4.1 命令模式的本質

命令模式的本質是:封裝請求。

4.2 何時選用

  • 如果需要抽象出需要執行的動作,并參數化這些對象,可以選用命令模式,把這些需要執行的動作抽象成為命令,然后實現命令的參數化配置

  • 如果需要在不同的時刻指定、排列和執行請求,可以選用命令模式,把這些請求封裝成為命令對象,然后實現把請求隊列化

  • 如果需要支持取消操作,可以選用命令模式,通過管理命令對象,能很容易的實現命令的恢復和重做的功能

  • 如果需要支持當系統崩潰時,能把對系統的操作功能重新執行一遍,可以選用命令模式,把這些操作功能的請求封裝成命令對象,然后實現日志命令,就可以在系統恢復回來后,通過日志獲取命令列表,從而重新執行一遍功能

  • 在需要事務的系統中,可以選用命令模式,命令模式提供了對事務進行建模的方法,命令模式有一個別名就是Transaction。

4.3 退化的命令模式

前面講到了智能命令,如果命令的實現對象超級智能,實現了命令所要求的功能,那么就不需要接收者了,既然沒有了接收者,那么也就不需要組裝者了。

參考

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容