1.初識命令模式
將一個(gè)請求封裝為一個(gè)對象,從而使你可用不同的請求對客戶進(jìn)行參數(shù)化;對請求排隊(duì)或記錄請求日志,以及支持可撤銷的操作。

- Command:
定義命令的接口,聲明執(zhí)行的方法。 - ConcreteCommand:
命令接口實(shí)現(xiàn)對象,是“虛”的實(shí)現(xiàn);通常會持有接收者,并調(diào)用接收者的功能來完成命令要執(zhí)行的操作。 - Receiver:
接收者,真正執(zhí)行命令的對象。任何類都可能成為一個(gè)接收者,只要它能夠?qū)崿F(xiàn)命令要求實(shí)現(xiàn)的相應(yīng)功能。 - Invoker:
要求命令對象執(zhí)行請求,通常會持有命令對象,可以持有很多的命令對象。這個(gè)是客戶端真正觸發(fā)命令并要求命令執(zhí)行相應(yīng)操作的地方,也就是說相當(dāng)于使用命令對象的入口。 - Client:
創(chuàng)建具體的命令對象,并且設(shè)置命令對象的接收者。注意這個(gè)不是我們常規(guī)意義上的客戶端,而是在組裝命令對象和接收者,或許,把這個(gè)Client稱為裝配者會更好理解,因?yàn)檎嬲褂妹畹目蛻舳耸菑腎nvoker來觸發(fā)執(zhí)行。
2.體會命令模式
2.1 場景問題——如何開機(jī)
當(dāng)我們按下啟動按鈕過后呢?誰來處理?如何處理?都經(jīng)歷了怎樣的過程,才讓電腦真正的啟動起來,供我們使用呢?
如果現(xiàn)在要求用軟件把開機(jī)的過程表現(xiàn)出來,該如何實(shí)現(xiàn)?
首先把開機(jī)的過程總結(jié)一下,主要就這么幾個(gè)步驟:首先加載電源,然后是設(shè)備檢查,再然后是裝載系統(tǒng),最后電腦就正常啟動了。可是誰來完成這些過程?如何完成?
不能讓使用電腦的客戶——就是我們來做這些工作吧,真正完成這些工作的是主板,那么客戶和主板如何發(fā)生聯(lián)系呢?現(xiàn)實(shí)中,是用連接線把按鈕連接到主板上的,這樣當(dāng)客戶按下按鈕的時(shí)候,就相當(dāng)于發(fā)命令給主板,讓主板去完成后續(xù)的工作。
另外,從客戶的角度來看,開機(jī)就是按下按鈕,不管什么樣的主板都是一樣的,也就是說,客戶只管發(fā)出命令,誰接收命令,誰實(shí)現(xiàn)命令,如何實(shí)現(xiàn),客戶是不關(guān)心的。
把上面的問題抽象描述一下:
客戶端只是想要發(fā)出命令或者請求,不關(guān)心請求的真正接收者是誰,也不關(guān)心具體如何實(shí)現(xiàn),而且同一個(gè)請求的動作可以有不同的請求內(nèi)容,當(dāng)然具體的處理功能也不一樣,請問該怎么實(shí)現(xiàn)?
2.2 使用模式的解決方案

要用程序來解決上面提出的問題,一種自然的方案就是來模擬上述解決思路。
在命令模式中,會定義一個(gè)命令的接口,用來約束所有的命令對象,每個(gè)命令實(shí)現(xiàn)對象是對客戶端某個(gè)請求的封裝,對應(yīng)于機(jī)箱上的按鈕,一個(gè)機(jī)箱上可以有很多按鈕,也就相當(dāng)于會有多個(gè)具體的命令實(shí)現(xiàn)對象。
在命令模式中,命令對象并不知道如何處理命令,會有相應(yīng)的接收者對象來真正執(zhí)行命令。就像電腦的例子,機(jī)箱上的按鈕并不知道如何處理功能,而是把這個(gè)請求轉(zhuǎn)發(fā)給主板,由主板來執(zhí)行真正的功能,這個(gè)主板就相當(dāng)于命令模式的接收者。
在命令模式中,命令對象和接收者對象的關(guān)系,并不是與生俱來的,需要有一個(gè)裝配的過程,命令模式中的Client對象就來實(shí)現(xiàn)這樣的功能。這就相當(dāng)于在電腦的例子中,有了機(jī)箱上的按鈕,也有了主板,還需要有一個(gè)連接線把這個(gè)按鈕連接到主板上才行。
命令模式還會提供一個(gè)Invoker對象來持有命令對象,就像電腦的例子,機(jī)箱上會有多個(gè)按鈕,這個(gè)機(jī)箱就相當(dāng)于命令模式的Invoker對象。這樣一來,命令模式的客戶端就可以通過Invoker來觸發(fā)并要求執(zhí)行相應(yīng)的命令了,這也相當(dāng)于真正的客戶是按下機(jī)箱上的按鈕來操作電腦一樣。

3.理解命令模式
3.1 認(rèn)識命令模式
3.1.1 命令模式的關(guān)鍵
命令模式的關(guān)鍵之處就是把請求封裝成為對象,也就是命令對象,并定義了統(tǒng)一的執(zhí)行操作的接口,這個(gè)命令對象可以被存儲、轉(zhuǎn)發(fā)、記錄、處理、撤銷等,整個(gè)命令模式都是圍繞這個(gè)對象在進(jìn)行。
3.1.2 命令模式的組裝和調(diào)用
在命令模式中經(jīng)常會有一個(gè)命令的組裝者,用它來維護(hù)命令的“虛”實(shí)現(xiàn)和真實(shí)實(shí)現(xiàn)之間的關(guān)系。如果是超級智能的命令,也就是說命令對象自己完全實(shí)現(xiàn)好了,不需要接收者,那就是命令模式的退化,不需要接收者,自然也不需要組裝者了。
而真正的用戶就是具體化請求的內(nèi)容,然后提交請求進(jìn)行觸發(fā)就好了。真正的用戶會通過invoker來觸發(fā)命令。
在實(shí)際開發(fā)中,Client和Invoker可以融合在一起,由客戶在使用命令模式時(shí),先進(jìn)行命令對象和接收者的組裝,組裝完成后,再調(diào)用命令執(zhí)行請求。
3.1.3 命令模式的接收者
接收者可以是任意的類,對它沒有什么特殊要求,這個(gè)對象知道如何真正執(zhí)行命令的操作,執(zhí)行時(shí)是從command的實(shí)現(xiàn)類里面轉(zhuǎn)調(diào)過來。
一個(gè)接收者對象可以處理多個(gè)命令,接收者和命令之間沒有約定的對應(yīng)關(guān)系。接收者提供的方法個(gè)數(shù)、名稱、功能和命令中的可以不一樣,只要能夠通過調(diào)用接收者的方法來實(shí)現(xiàn)命令對應(yīng)的功能就可以了。
3.1.4 智能命令
在標(biāo)準(zhǔn)的命令模式里面,命令的實(shí)現(xiàn)類是沒有真正實(shí)現(xiàn)命令要求的功能的,真正執(zhí)行命令的功能的是接收者。
如果命令的實(shí)現(xiàn)對象比較智能,它自己就能真實(shí)地實(shí)現(xiàn)命令要求的功能,而不再需要調(diào)用接收者,那么這種情況就稱為智能命令。
也可以有半智能的命令,命令對象知道部分實(shí)現(xiàn),其它的還是需要調(diào)用接收者來完成,也就是說命令的功能由命令對象和接收者共同來完成。
3.1.5 發(fā)起請求的對象和真正實(shí)現(xiàn)的對象是解耦的
請求究竟由誰處理,如何處理,發(fā)起請求的對象是不知道的,也就是發(fā)起請求的對象和真正實(shí)現(xiàn)的對象是解耦的。發(fā)起請求的對象只管發(fā)出命令,其它的就不管了。
3.1.6 命令模式的調(diào)用順序示意圖
使用命令模式的過程分成兩個(gè)階段,一個(gè)階段是組裝命令對象和接收者對象的過程,另外一個(gè)階段是觸發(fā)調(diào)用Invoker,來讓命令真正執(zhí)行的過程。

再看看真正執(zhí)行命令時(shí)的調(diào)用順序示意圖:

3.2 參數(shù)化配置
所謂命令模式的參數(shù)化配置,指的是:可以用不同的命令對象,去參數(shù)化配置客戶的請求。
像前面描述的那樣:客戶按下一個(gè)按鈕,到底是開機(jī)還是重啟,那要看參數(shù)化配置的是哪一個(gè)具體的按鈕對象,如果參數(shù)化的是開機(jī)的命令對象,那就執(zhí)行開機(jī)的功能,如果參數(shù)化的是重啟的命令對象,那就執(zhí)行重啟的功能。雖然按下的是同一個(gè)按鈕,相當(dāng)于是同一個(gè)請求,但是為請求配置不同的按鈕對象,那就會執(zhí)行不同的功能。
3.3 可撤銷的操作
可撤銷操作的意思就是:放棄該操作,回到未執(zhí)行該操作前的狀態(tài)。這是一個(gè)非常重要的功能,幾乎所有GUI應(yīng)用里都有撤消操作的功能。GUI的菜單是命令模式最典型的應(yīng)用之一,所以你總是能在菜單上找到撤銷這樣的菜單項(xiàng)。
既然這么常用,那該如何實(shí)現(xiàn)呢?
有兩種基本的思路來實(shí)現(xiàn)可撤銷的操作,一種是補(bǔ)償式,又稱反操作式:
- 比如被撤銷的操作是加的功能,那撤消的實(shí)現(xiàn)就變成減的功能;同理被撤銷的操作是打開的功能,那么撤銷的實(shí)現(xiàn)就變成關(guān)閉的功能。
- 另外一種方式是存儲恢復(fù)式,意思就是把操作前的狀態(tài)記錄下來,然后要撤銷操作的時(shí)候就直接恢復(fù)回去就可以了。
這里先講第一種方式,就是補(bǔ)償式或者反操作式,第二種方式放到備忘錄模式中去講解。
實(shí)例如下:
考慮一個(gè)計(jì)算器的功能,最簡單的那種,只能實(shí)現(xiàn)加減法運(yùn)算,現(xiàn)在要讓這個(gè)計(jì)算器支持可撤銷的操作。
3.4 宏命令
簡單點(diǎn)說就是包含多個(gè)命令的命令,是一個(gè)命令的組合。
舉個(gè)例子來說吧,設(shè)想一下你去飯店吃飯的過程:

3.4.1 宏命令在哪里?
現(xiàn)實(shí)中是當(dāng)你你點(diǎn)完菜,說“點(diǎn)完了”的時(shí)候,服務(wù)員才會啟動命令的執(zhí)行,請注意,這個(gè)時(shí)候執(zhí)行的就不是一個(gè)命令了,而是執(zhí)行一堆命令。
描述這一堆命令的就是菜單,如果把菜單也抽象成為一個(gè)命令,就相當(dāng)于一個(gè)大的命令,當(dāng)客戶說“點(diǎn)完了”的時(shí)候,就相當(dāng)于觸發(fā)這個(gè)大的命令,意思就是執(zhí)行菜單這個(gè)命令就可以了,這個(gè)菜單命令包含多個(gè)命令對象,一個(gè)命令對象就相當(dāng)于一道菜。那么這個(gè)菜單就相當(dāng)于我們說的宏命令。
3.4.2 如何實(shí)現(xiàn)宏命令
宏命令從本質(zhì)上講類似于一個(gè)命令,基本上把它當(dāng)命令對象進(jìn)行處理。但是它跟普通的命令對象又有些不一樣,就是宏命令包含有多個(gè)普通的命令對象,執(zhí)行一個(gè)宏命令,簡單點(diǎn)說,就是執(zhí)行宏命令里面所包含的所有命令對象,有點(diǎn)打包執(zhí)行的意味。
3.5 隊(duì)列請求
所謂隊(duì)列請求,就是對命令對象進(jìn)行排隊(duì),組成工作隊(duì)列,然后依次取出命令對象來執(zhí)行。多用多線程或者線程池來進(jìn)行命令隊(duì)列的處理,當(dāng)然也可以不用多線程,就是一個(gè)線程,一個(gè)命令一個(gè)命令的循環(huán)處理,就是慢點(diǎn)。
3.6 日志請求
所謂日志請求,就是把請求的歷史記錄保存下來,一般是采用永久存儲的方式。如果運(yùn)行請求的過程中,系統(tǒng)崩潰了,那么在系統(tǒng)再次運(yùn)行時(shí),就可以從保存的歷史記錄里面獲取日志請求,并重新執(zhí)行命令。
日志請求的實(shí)現(xiàn)有兩種方案:
- 一種就是直接使用Java中的序列化方法;
- 另外一種就是在命令對象里面添加上存儲和裝載的方法,其實(shí)就是讓命令對象自己實(shí)現(xiàn)類似序列化的功能。
當(dāng)然要簡單就直接使用Java中的序列化。
3.7 命令模式的優(yōu)缺點(diǎn)
- 更松散的耦合
- 更動態(tài)的控制
- 能很自然的復(fù)合命令
- 更好的擴(kuò)展性
4.思考命令模式
4.1 命令模式的本質(zhì)
命令模式的本質(zhì)是:封裝請求。
4.2 何時(shí)選用
如果需要抽象出需要執(zhí)行的動作,并參數(shù)化這些對象,可以選用命令模式,把這些需要執(zhí)行的動作抽象成為命令,然后實(shí)現(xiàn)命令的參數(shù)化配置
如果需要在不同的時(shí)刻指定、排列和執(zhí)行請求,可以選用命令模式,把這些請求封裝成為命令對象,然后實(shí)現(xiàn)把請求隊(duì)列化
如果需要支持取消操作,可以選用命令模式,通過管理命令對象,能很容易的實(shí)現(xiàn)命令的恢復(fù)和重做的功能
如果需要支持當(dāng)系統(tǒng)崩潰時(shí),能把對系統(tǒng)的操作功能重新執(zhí)行一遍,可以選用命令模式,把這些操作功能的請求封裝成命令對象,然后實(shí)現(xiàn)日志命令,就可以在系統(tǒng)恢復(fù)回來后,通過日志獲取命令列表,從而重新執(zhí)行一遍功能
在需要事務(wù)的系統(tǒng)中,可以選用命令模式,命令模式提供了對事務(wù)進(jìn)行建模的方法,命令模式有一個(gè)別名就是Transaction。
4.3 退化的命令模式
前面講到了智能命令,如果命令的實(shí)現(xiàn)對象超級智能,實(shí)現(xiàn)了命令所要求的功能,那么就不需要接收者了,既然沒有了接收者,那么也就不需要組裝者了。