設計模式中的每個模式描述了一個在咱們周圍不斷重複發生的問題,以及該問題的解決方案的核心。這樣,你就能一次又一次地使用該方案而沒必要作重複勞動。
一個設計模式,它的服務對象是高層模塊,在設計模式中稱爲客戶端,所以在描述設計模式的時候都是以客戶端做爲使用方來進行描述的。
設計模式在類間關係這個粒度上給出常見問題的解決方案。屬於軟件工程中邏輯架構設計中至關重要的一環。html
快速查閱各種設計模式使用場景可參考此文:設計模式大全。java
定義: 把一個請求看作一個對象。這樣用戶就能夠參數化客戶端請求,實現請求的隊列操做和日誌管理,而且支持對操做進行撤銷回退。(Encapsulate a request as an object, thereby letting users parameterize clients with different requests, queue or log requests, and support undoable operations.)git
概述: 在軟件開發過程當中,咱們可能會遇到這樣的問題,在程序運行前,不知道命令(本文中請求和命令都指代同一個東西,即客戶端發起的一個須要處理的操做)的執行者是誰,也不知道須要執行哪一個命令。這時,咱們可使用命令模式將命令的發起者和命令的執行者解耦。把命令化做一個相對獨立的對象,在發送者和執行者之間傳輸。程序運行時,再肯定須要執行的命令和其執行者,並將命令移交給命令管理器處理。若是對命令模式稍微作一點改動,讓每一個命令提供執行的同時提供一個撤銷的方法,就可以實現對命令的無限次撤銷和重作,這在工程中具備很是普遍的應用。數據庫
其實定義就已經講出了命令模式的核心思想:把一個命令看作一個對象。這樣作就能讓命令擁有全部對象所擁有的優點。模式中的其餘參與者都是圍繞它來運做的。編程
客戶端|Client: 命令的發起者。肯定接下來要執行什麼命令。
調用者|Invoker: 命令的管理者,不關心每一個命令具體是作什麼內容,根據客戶端的指示按序執行命令。
命令接口|ICommand:命令接口協議,肯定每一個命令須要提供的功能,這裏要求每一個命令類都提供執行方法。
具體命令|ConcreteCommand:包含執行一個命令所需的全部上下文信息,例如執行接收者的哪一個方法,以及方法所須要的參數,甚至命令做爲GUI 顯示時的相關信息,例如應該顯示的圖標路徑。具體命令類是命令模式中的核心節點,須要重點理解。設計模式
接收者|Receiver:命令所對應任務的實際執行者,位於調用鏈條的末端。服務器
注意:客戶端發起的每個命令都是一個對象,即便是兩條相同的命令,也是兩個對象,而不是一個對象使用兩次。網絡
下面經過一個隱喻來直觀地展示命令模式的各個組成部分。架構
咱們假設這樣一個場景,公元 6011 年,人類在銀河系的上千的星球創建了殖民地,其中大唐和蓋亞是兩個敵對的國家,大唐準備發動一場戰爭,冷月是一個大型特種部隊的指揮官,擁有護衛艦、運輸艦和戰列艦等單位的使用權,該部隊具備偵查、暗殺、破壞等多種職能。冷月所在星球只有一個小型港口提供艦船的發射與停靠,有一個指揮塔負責艦船的調度。lua
某天,冷月派遣一個破壞小隊使用戰列艦前往蓋亞一個冷僻星球進行破壞佔領,派遣一個偵查小隊使用護衛艦先行偵查。同時派遣一個偵查小隊使用運輸艦前往友軍三十四軍團處待命。冷月將艦船出港許可發送給指揮塔,讓指揮塔安排艦船出港。
在上面這個場景中,冷月是命令的發起人,是一個 Client,他根據實際的戰鬥須要安排艦船和做戰人員。每一艘艦船都至關因而一個具體命令對象,艦長知道應該把艦船運送到什麼地方(類比命令的執行環境),也知道艦船的乘坐人員(命令的接收者),指揮塔是 Invoker,它不關心出港艦船的類型以及艦船的運載人員,只是接受冷月的艦船的出港請求,而後安排對應艦船出港。當艦船到達目的地以後,艦船運載的做戰人員會去執行具體的做戰任務。
(筆者構思了好幾種場景,發現命令對象化在現實中比較難以找到對應物,一個是由於命令對象具備接收者的引用,具備主動性,若是是人引用人會比較奇怪,相對來講具備主動性的機器容器引用人會比較合適,另外一方面則是命令對象在 Client 眼中和 Invoker 眼中有不同的抽象層次,也比較難以找到現實的對應物。科幻戰爭的這個場景雖然可以闡述命令模式的幾個關鍵要素,可是場景自己有較多的干擾元素,有點過於複雜了,在實際中指揮塔不可能不關心艦船的類型,艦船做爲命令的基本單位也有一點牽強。不過場景重在乎會,加深理解,筆者認爲該場景仍是可以達成這一點,就放上來了,各位看官若是有更好的隱喻,歡迎在文後回覆。)
適用於各類須要對命令進行調度管理以及傳輸的場景,也就是各類須要把命令包裝成命令對象的場景。
每一個事務做爲一個命令對象。
每一個線程做爲一個命令對象,客戶端負責提供接收者
每一個命令都須要一個具體命令類,若是系統的命令繁多,可能會影響開發效率。
命令模式在工程中的許多應用是很是基礎性的,在遊戲與非遊戲項目中都能使用。下面單以遊戲應用爲例進行介紹:
在控制玩家輸入的時候,使用命令模式可使得按鍵和按鍵對應的命令解綁,從而支持用戶自定義,還能夠把指令的觸發和執行時間解綁,實現延時執行。對於多角色遊戲,還能爲玩家和AI之間提供一套通用的命令接口
所謂宏,就是將一些命令組織在一塊兒,做爲一個單獨命令完成一個特定任務。而宏記錄是指將用戶的一系列命令記錄下來,將記錄下來的一組命令做爲一個宏。經過宏記錄可以實現對玩家全部操做的錄像。
GM 指令通常來講可以模擬玩家操做來快速得到遊戲的一些資源或者推動遊戲進度。若是爲玩家操做的命令類型實現一個 toScript() 方法,將玩家的一個操做直接轉換成一個可執行的 GM 指令,那麼就能夠直接經過宏記錄執行 GM 指令回放玩家的操做。關於這個 toScript() 方法稍微再解釋一下,通常遊戲裏面會嵌入腳本引擎,例如 lua ,所以能夠方便地將一個操做轉化成可執行的 lua 腳本。
命令對象能夠方便地在網絡中進行傳輸,這樣一個一個指令就能夠同時在多個機器上運行。例如在移動遊戲的PVE戰鬥中,客戶端會先將玩家在戰鬥中的全部操做記錄下來,在戰鬥結束後將全部操做上傳給服務器再從新計算一遍,確保客戶端沒有做弊。
將每一個須要加載的操做做爲命令對象,並給命令對象實現一個加載時間預估的方法,可能使得進度條可以較爲精準得反映加載進度。
使用命令模式能夠實現某些類型新手引導的無縫切入,例如某個新手引導只須要玩家一路點擊 next 直到點擊確認進入實際的遊戲環境。那麼能夠把這個新手引導做爲一個命令對象,在須要展現該引導的時候新建命令對象,讓該命令對象處理 next 的交互邏輯與每一個引導頁的展現邏輯,等到完成的時候,命令對象直接調用傳入接收者的執行方法完成轉換。
筆者實現了一個簡單的 TicTacToe 遊戲來介紹命令模式以及其撤銷重作的變體。 TicTacToe 的基礎遊戲邏輯參考了 Andrew Arnott 的 Java 實現,在第三節中有相關引用。
筆者將案例上傳在這裏: https://git.coding.net/tangyikejun/TicTacToeInCommandPattern.git
README 中有對每一個代碼文件的基本說明。
命令模式在遊戲開發中真的十分經常使用,能夠說是不可避免會遇到的一個模式。其撤銷、重作變體更是應用普遍,隨處可見該模式的身影。但願各位看官可以掌握命令模式。
另外一方面該模式的專用性也比較強,在一個遊戲項目中,可以抽象成命令的概念並很少,可是一旦遇到了就能比較容易的聯想到,是個比較容易識別的模式。
遊戲中使用頻度評級:★★★★☆
Game Programming Patterns[via Robert Nystrom] (貌似是本很不錯的講遊戲中設計模式的書,美中不足沒有中文版,Unity主程大大的平常(阿斌) 正在翻譯。在命令模式一節中,做者詳細介紹了該模式在遊戲角色控制中的應用,而且介紹了撤銷重作功能做爲命令模式的一個變體該如何實現。)
遊戲開發設計模式之命令模式(unity3d 示例實現)[via wolf96 in CSDN] (博主大概是根據 Game Programming Patterns 寫了一個 Unity上的實現,寫的沒那麼詳細,可是淺顯易懂,也不錯。)
Let Your Players Undo Their In-Game Mistakes With the Command Pattern[via Andrew Arnott](使用 Java 實現了井字棋遊戲的撤銷和重作功能,雖然也是用命令模式,可是實現的方式與 Game Programming Patterns 有一點點不同,這裏使用了一個 Command Manager 來管理命令,內部分別保存 undo 和 redo 的命令棧,而不是隻使用一個棧。)
"Command" design pattern for games[](做者提到使用命令模式須要解決的兩個實際問題,可是彷佛並無人給出合適的解決方案)
遊戲編程模式- 再探Command模式[via 仙道菜](遊戲編程模式- 再探Command模式 的一個翻譯)
《JAVA與模式》之命令模式[via java_my_life](裏面提到一個錄音系統的示例,並非很貼合這個模式,可是裏面提到了一點,使用命令模式能夠方便得實現複合命令,即宏命令)
圖說設計模式-命令模式[via me115](對命令模式相對介紹的比較詳細,示例代碼用 C++ 給出)
Command pattern-WiKi(對命令模式的概念以及應用領域作了十分詳細的說明,本文的應用部分參考了其中的內容)