策略模式筆記

策略模式的小小心得記錄

設計謎題(類別名稱取自於Head first in Design pattern):

說明:現在假如有一個需求,要你在一個游戲的角色設計中,加入一個使用斧頭以及使用寶劍等武器的行為。你可能會這樣做:

Pic1

不過…若透過繼承加入行為,你會發現某些角色會得到不該得到的能力,例如,Queen不能使用斧頭,但是他卻不合理的得到超類別所給他的使用斧頭的能力,就像你會看到一個瘦小唯美的Queen角色揮著大斧(雖然這現在看起來似乎也滿合理的,不過假如是現實的話,那麼大的斧頭哪裡揮的動啊),這時就必須透過繼承覆寫的方式來實作各個角色的行為模式。例如Queen不能用劍以及斧頭,可以重新覆寫他的行為,變成不實踐。

但假如愈來愈多的武器推出呢?每次都要去檢視角色改寫其行為,這不是非常的麻煩嗎?大家都知道Online Game是很常更新的,常常規格就會加入新的武器,甚至是新的角色。這樣使用繼承的關係的話,每次不都要改到死,而且加入新角色的時候都要被迫去檢視與可能要覆寫的戰鬥行為,那不就非常的沒有變動彈性了嗎?

 

那麼利用介面如何?

像下圖:

Pic2

(註:Knight類別要拉線到介面useSword喲)

這好像也不是個好主意,雖然不會有不合理的行為!!但是重複的程式碼變的非常的多,假如現在又有二十個新角色的話,至少要稍微覆寫個20次,應該不是個好下場吧…

 

透過繼承好像有一些缺失,因為對於角色局部的改變就會影響到所有的次類別的角色行為,這並不洽當,而useSword跟useAxe看起來合理多了!至少不會有所謂得到不該得的能力的不合理行為。但透過介面,就沒有辦法達到程式的再利用,使用介面會綁死你的程式碼。

 

幸運的,Head First in Design Pattern教了我第一課,有一個設計模式適合解決這樣的問題。(可以試想上一篇的鴨子問題)

是的,標題寫的很明白:策略模式

我們總是希望一切都是有彈性的,因為很多時候的設計並沒有彈性。

follow下面看看吧

設計守則1:找出程式中可能需要更動之處,把它們獨立出來,不要和那些不需要更動的程式碼混在一起。

分開變動和不會變動的部分,例如類別與行為,若是行為常常會需要更動,那麼,行為原本從類別中定義的部分,不如直接拿出來做為個別的類別。個別建立新類別代表每個行為。

例如,遊戲中的角色都會走、都可以買東西,而且都會攻擊,這邊較不會變動的東西,我們可以繼續放在超類別”角色”中,透過次類別呼叫他們的行為!但武器可能很多種,每次不同角色都可以拿不同的武器-->看起來會像這樣

Pic3

這樣有什麼好處,至少我們知道未來的變動,我們可以直接分辨出哪些是我們要更動,當然,沒有什麼是完全不會變的。至少需求一定是最常改變的,不過這邊除了一些小改變,我們並不打算對角色這個類別做太多的處理。

 

設計守則2:

寫程式是針對超類別或介面而寫的,而不是針對實踐方式而寫

老樣子,希望一切能有彈性,甚至我們讓角色的行為可以動態地改變呢?

或許我們應該在類別中包含設定行為的方法,這樣就可以在「執行期」動態地「改變」實體的行為,這樣聽起來就很理想。

 

這邊說明一下書中所說明的寫程式是為了介面而寫的含意。下面我自己舉個例,看看

是否能看出當中的奧妙:

Pic4

這就是如何動態地指派實體的行為的例子。而且我們之後只要在執行期才指定實踐的物件就好了!

所以這個角色扮演遊戲的例子就是如下:

Pic5

我們把角色的行為透過介面,用各類別負責來實踐到時具體角色所對應的行為。

 

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

更多的整合該來了,剛剛說到如何在執行期間動態的分配給角色該有的行為?

我們可以在角色的超類別宣告一個WeaponBehavior型態的變數,之前次類別跟武器有關的行為也要移除,因為都已經被移到WeaponBehavior的介面下的類別了。

接著我們可以來實踐超類別角色的fight行為()

如下:

   1:  
   2: public Class Character
   3:  WeaponBehavior weapon;
   4:  //諸如此類
   5:  public fight(){
   6:  weapon_behavior.useWeapon(); 
   8:  //我們關心fight行為能正確的使用他該用的武器即可
   9:  }
  10: }

那麼就像上面所說的,我們如何在執行期動態的分配給角色該有的行為呢?

我們可以這樣做

   2: public class knight : Character{
   3:  knight(){ //初始化
   4:  weapon = new SwordBehavior( ); 
   6:  }
   7: }

註:上述程式好像違反了之前所說不要針對實踐類別(例如queen)來寫程式嗎?對,沒錯

聽說這本書日後會教怎麼修正這個問題的模式,不過,目前的作法仍是很有彈性的,只有在初始化不夠彈性而己,所以日後我有看到再聊這個部分吧。

那麼再來,如何在執行期改變角色的行為呢?

我們只要在角色類別(Character)中加入下列的新方法就可以了

   1: public void 
   2: SetWeaponBehavior(WeaponBehavior wb){
   3: WeaponBehavior = wb;
   4: }

好了,縱觀上述,整個程式如何動態的去反應規格的變動呢?

從下面簡單來看

Step1.加入新的角色型態Archer(弓箭手)

   1: public class Archer : Character{
   2:  public Archer(){
   3:  weapon = new 
   4: sword(); //弓箭手都會有一把新手劍(攻擊力很弱)
   5:  }
   6: }

 

加入弓箭的攻擊行為!

   1:  
   2: public class BowAndArrowBehavior : WeaponBehavior { //實作介面
   3:  useWeapon(){
   4:  //實踐用弓與箭射擊敵人的程式
   5:  }
   6: }

 

而在主程式我們可以這樣宣告…

   1: public static void Main(){
   2:  Character Mary = new Archer(); //宣告一個帳號叫Mary的角色
   3:  Mary.fight(); //結果是使用劍攻擊
   4:  Mary.setWeaponBehavior(new 
   5: BowAndArrowBehavior());//設定弓箭行為
   6:  Mary.fight(); //結果是使用弓射擊
   7: }

 

上述程式成功的話,就表示可以讓弓箭手動態地改變行為(這個例子假如用職業來分會更一致就是了)

 

那整個模式我們來回顧一下,透過封裝,我們不在把角色的行為說成是一組行為,而開始想成是一群演算法。演算法代表角色能做的事情,可以試著去套用到其他的事情上,這個角色遊戲就是一個例子。

Pic6

 

這個就是策略模式的一個簡單範例,說簡單也不容易,來看看書中對策略模式的定義吧:

 

策略模式定義了「演算法」家族,個別封裝起來,讓它們之間是可以互相替換的,此模式讓演算法的變動,不會影響到使用演算法的程式。

 

後記:書中的例子是透過鴨類別來說明策略模式,說的很靈活生動。而這篇裡面所有的類別名稱其實都是書上記載的,雖然練習很簡單,也不是讓我們去回溯純繼承與介面的寫法,不過這個例子使我反向重新走過一遍,用繼承與介面實作這樣的一個流程,來思考策略模式的思維,也證明了我以前沒學過策略模式之前,一直對策略模式有著大大的誤解,以為策略兩個字有著另外一組意思。看過這篇後也大大的改觀了,迫不及待的上下一課呢。希望下次還有機會分享囉!

 

ps.若是看過這本書,就當這篇只是我的私人筆記就好了> <