[Design Patterns-Python]一.策略模式

Daniel Chiang
Jan 27, 2021

--

情境

今天你被指派做Angry Bird遊戲。在剛開始,公司對於這個遊戲還沒有很完整的規劃,希望你能先把裡面的腳色作出來,並且能夠作出飛行動作,最好還能夠發出一些聲音增加更多的趣味。

實做

一開始有的資訊還不夠充足,我會這樣簡單的實做不同腳色鳥。

  1. 建立一個AngryBird超類別,並實踐fly, tweet的方法。
  2. 建立兩隻鳥類別(Red, Chuck)繼承AngryBird。

情境-新需求

這時候老闆心血來潮跟你說,我覺得每隻鳥都要有不同的特色阿,我希望Chuck飛快一點,而且牠是隻不會叫的鳥。

現在你發現了一個問題,當所有鳥都繼承了AngryBird,子類別繼承了父類別所有的行為,甚至是不該具備的行為,並且父類別做任何的修改對於子類別都有很大的影響,這次使用繼承的一大缺點: 修改父類別容易破壞子類別

現在的需求希望Chuck飛快一點,並且不能叫,因此你必須覆寫fly、tweet兩個行為。可怕的還是老闆希望每隻鳥都要有特色,因此你現在每新增一隻鳥都很可能要覆寫次類別的方法。

問題:

  1. 行為(fly、tweet)會隨著鳥不同而改變。
  2. 繼承影響了所有次類別(鳥)。

設計模式實作

改寫行為

現在我要將常變動行為(父類別的方法)抽出來,讓行為類別實踐行為介面,而不是透過AngryBird實踐行為介面。之後憤怒鳥就不需要知道行為的實踐細節,我們能讓每隻鳥”指定”自己的行為。

改寫AngryBird

我們現在不想使用繼承,因為這讓類別之間關係太緊密。因此我們使用合成的方式將類別鬆綁(當你將兩個類別以上組合在一起就稱作合成),現在AngryBird不需要自己實踐行為,而是將行為”委由(delegate)”別人處理(FlyInterface, TweetInterface)。

  1. 在初始化傳入fly_behavior, tweet_behavior的物件。
  2. 改寫fly、tweet方法,不親自處理這些行為,而是委由fly_behavior, tweet_behavior物件幫它執行。
  3. 而子類別只需要繼承AngryBird並將指定的行為物件當作參數傳進去。

我們回頭看看我們做了些什麼,我們發現AngryBird的行為會因為不同種類的鳥會有不同的行為,因此我們把它抽出來。

設計守則: 程式中常更動的部分,把它從不常更動的地方獨立出來。

抽離出來定義了兩個介面,封裝各自的演算法,行為針對各自的介面實作,好處就是能達到"多型"(如果還不知道多型請回到[Design Pattern-Python]設計模式-前言 複習一下)
接下來是重點,繼承有一些缺點,繼承只能代表其中一條的變異線,並且在類別之間關係非常緊密。我們修改AngryBird將do_fly、do_tweet的行為委由傳入的物件參數(fly_behavior, tweet_behavior)代為處理這使得鳥的行為不是繼承而來,而是由自己指定的行為物件合成而來,大大的提高了彈性。

設計守則: 多用合成,少用繼承。

定義

策略模式(Strategy Pattern): 定義一系列演算法家族,允許他們之間相互替換,並且不會影響到使用演算法的程式。

依照我們範例,就是參照FlyInterface介面封裝了一系列飛行的類別,這些就是飛行的家族,因為都是實作同一個介面達到多型的效果,同一家族的飛行演算法可以相互交替使用並不影響憤怒鳥。

因為使用合成提升了變動的彈性,你可以輕鬆達到在執行期間動態地更動行為,只要在AngryBird新增兩個新方法修改行為。

如果是我們最一開始的寫法會把行為的實踐綁死在憤怒鳥的子類別中,而現在你可以在執行期間改變行為,並且被封裝起來的演算法可以再利用。

結論

策略模式算是比較簡單的模式,但是卻涵蓋了相當重要及常見的設計守則,比起模式的定義,我覺得更重要的是設計守則好的設計應該是能達到可再利用、可維護、可擴充三特性
此模式範例的程式碼可以參考我的github

下一篇接著要講的是狀態模式,狀態模式跟策略模式結構上有點相似但目的不同,如果你了解了策略模式就繼續往下看看狀態模式吧。

>> 下一篇: [Design Patterns-Python]二.狀態模式

--

--