設計模式(一):“穿越火線”中的“策略模式”(Strategy Pattern)
在前段時間呢陸陸續續的更新了一系列關於重構的文章。在重構我們既有的程式碼時,往往會用到設計模式。在之前重構系列的部落格中,我們在重構時用到了“工廠模式”、“策略模式”、“狀態模式”等。當然在重構時,有的地方沒有點明使用的是那種設計模式。從今天開始,我們就圍繞著設計模式這個主題來討論一下我們常用的設計模式,當然“GoF”的23種設計模式不會全部涉及到,會介紹一些常見的設計模式。在接下來我們要分享的設計模式這個系列部落格中,還是以Swift語言為主來實現每種設計模式的Demo。並且仍然會在GitHub上進行Demo的分享,希望與大家相互交流,相互學習,有不足之處還望批評指正。
今天部落格的主要思路是先圍繞著“穿越火線”中的角色與武器的關係,通過策略模式來設計實現這種關係,整體的來整體感受一下“策略模式”的優點。然後再參考《Head First Design Patterns
一、穿越火線中的“策略模式”(Strategy Pattern)
當然,這個示例是我YY出來的示例,不是“穿越火線”這個遊戲的設計方案呢。說到"穿越火線"如果你沒有玩過,那應該聽過吧,就是“CrossFire”。我平時不怎麼玩遊戲,穿越火線之前體驗過,不過只有被爆頭的份兒。聽說那些遊戲玩家現在不怎麼玩兒“CF”啦,改玩兒Dota,LOL啦,真的是這樣嗎?我個人對於遊戲而言是外行了,不過玩個超級瑪麗、魂鬥羅、植物大戰殭屍、節奏大師
言歸正傳,今天我們就模擬穿越火線中角色和武器的關係,使用“策略模式”來實現。首先我們先分析一下這個場景,穿越火線中角色分為不同的等級,也就是“軍銜”了,簡單的說幾個吧,由高到底對應著“軍師旅團營連排小工兵”,上面的是組織,軍銜莫過於各種級的士官,少中上尉,少中上校,少中上將(應該對吧,本人不太專業呢,不過用於咱們要實現的例子是夠了)。我雖然不怎麼會打CF,可是我會玩軍棋呢。
我是不是刷知乎刷多了,不能在這兒“一本正經的胡說八道”了。言歸正傳,不同的角色所配備的武器裝備也不同,等級越高所使用的武器裝備也就越厲害。我們如何使用面向物件來表達這種角色與武器之間的關係呢?我們先看一下下方的類“類圖”。
上面是一個簡化的類“類圖”,上面這種形式可以表達我們之前的那種場景。“軍人”是一個父類,其他具體等級的軍官都繼承自“SuperClass”。那麼問題來了,在上面那種模式下,如果只有“少尉”和“中尉”配備某種武器,其他軍官不配備,我們就要在“少尉”和中尉的類中分別新增要實現的武器,那麼這樣會產生冗餘的程式碼。還有個問題是上面的設計形式不利於擴充套件,比如“少尉”也要配備狙擊步槍,豈不是得從“中尉”中的狙擊步槍的方法複製到“少尉”中。這樣也會產生重複程式碼的。那麼我們該怎樣去解決這個問題呢?
有童鞋說了,在Swift中的Protocol(協議,也就是Java中的介面)可以提供預設的實現。也就是宣告一個protocol,然後通過extension來為協議新增預設實現,只要是類遵循該協議,那麼這個類就擁有了這個預設實現(當然,Java中的介面是不能通過後期的延展來為其新增預設實現的)。如果在Swift中使用介面的預設實現的話,如果要對上述軍官擴充裝備的話,設計中的類“類圖”(不是類圖,但與類圖相似)實現如下所示:
上面這種設計模式雖然不會產生重複的程式碼,但是如果給“軍官”新增的武器過多的話,那麼會導致相應的類中實現的介面過多,這並不是我們想要的。下方將會給出一個良好的解決方案,也就是使用策略模式。
二、使用“策略模式”(Strategy Pattern)對上述關係進行設計
“策略模式”的定義大概是:策略模式,將不同的策略(演算法)進行封裝,讓他們之間可以相互的替換,此模式讓策略的變化獨立於使用策略的使用者。在設計模式中有不同的設計原則,其中有一條就是“找出程式中可能需要變化的地方,並且把它嗎獨立出來,不要和不變的程式碼混在一起”。根據這條設計原則,然後結合著上述示例不難分析出來,在上述示例中,使用軍官使用的不同武器是可以變化的,使用不同的武器正是採取不同的策略呢。
所以經過上述討論,我們可以使用“策略模式”來重新設計上面的結構。簡單的說就是把變化的“武器”部分進行提取,然後在軍官中進行使用,不同的軍官可以採取不同的策略,並且可以隨時替換。下面是我們使用“策略模式”重新設計後的關係,具體請看下圖。
在上面的類“類圖”中我們對可變的“武器策略進行了提取”。我們使用了WeaponBehavior協議來規定武器的策略,使得不同的武器對外有統一的介面,在此就是使用武器,也就是開火。不同的武器使用不同的的“開火策略”,但是對外的介面都是一樣的。設計原則中有一條是“面向介面程式設計,而不是面向實現程式設計”。這裡所指的介面可以是協議,可以是抽象類,也可以是超類,其實就是利用面向物件的“多型”特性。上面的紅框中實現的就是所有不同的策略。
而綠框中是我們的使用者,也就是軍官的定義,是我們不變的部分。在軍官中也有一個基類,在基類中定義了軍官的共性,其中依賴於“武器策略”的介面。在軍官超類中使用“武器策略”的協議聲明瞭一個物件,該物件就是該軍官所採取的武器策略。在軍官的超類中可以通過setWeapon()方法採取不同的策略,其中fire()方法就是使用該“武器策略”進行開火。在具體的軍官中的changeXXX()方法就是呼叫setWeapon()方法進行策略切換的方法。具體內容請看下方的具體實現。
三、上述“策略模式”(Strategy Pattern)的具體實現
上面給出了“武器策略模式”的個個部分之間的關係,並給出了相應的解釋。如果對此你感覺到抽象的話,那麼我們接下來就用相應的Swift程式碼去實現上述示例。也就是將上面的理論部分進行具體實現,當然在此我們用的是Swift語言,但是,你完全可以使用其他的面向物件程式語言。下面就是我們具體的程式碼實現。
下方就是我們對“武器策略”的實現,紅框中對應的就是上面圖中的WeaponBehavior(協議)介面,下方綠框中就是不同武器的策略,每個武器策略都遵循了WeaponBehavior協議。並且實現了相應的useWeapon()方法。
對“武器策略”模組實現完畢後,接下來我們就得實現軍官模組了。也是根據上面我們所畫的“模式結構圖”來實現我們的“軍官模組”,下方Character就是所有軍官的基類,其中預設的武器策略weapon就是手槍(PistolBehavior),其中有設定策略和改變策略的方法,並且還有使用策略的方法(fire())。下方的紅框就是實現的不同的軍官了,不同的軍官可以有不同的切換策略的方法。具體如下所示:
上面就是我們全部實現的程式碼,下方是我們的測試用例和輸出結果。下方我們建立了一個“中尉”軍官----lieutenant,軍官預設的是開的手槍。但是可以呼叫相應的changeXXX()方法來切換武器策略。開手槍時,發現火力不行,然後就呼叫changeHK()方法切換到HK48步槍。這種關係使用“策略模式”就比較靈活,並且便於擴充套件。比如中尉現在也要配備大狙,因為現在已經有大狙這個武器策略了,所以我們現在只需在中尉中新增相應的change方法,傳入大狙的武器策略即可,具體的就不在演示了。
本來想著在結合著《Head First Design Patterns》這本書中的鴨子示例在聊一下“策略模式”呢,由於篇幅有限,今天的部落格到這兒吧。對於“策略模式”上面是一個完整的示例。