close

What: 何謂策略模式?

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


Why: 為什麼要使用策略模式?

--因為可以動態的更換所使用的演算法而不變動到使用演算法的程式,此模式捨棄繼承而改用合成,降低程式間的耦合度。

 

When: 什麼時機適合用此模式?

--當需要頻繁的更換或動態的指定所使用的演算法而不變動到使用演算法的程式時,就是用此模式。

 

How: 若要使用此模式,如何使用呢?

--從上面的敘述中會發現幾個問題需要釐清:

1. 何謂演算法家族? 何謂演算法個別封裝?

2. 何謂在演算法家族間彼此替換,卻不影響到使用此演算法的程式?

3. 何謂頻繁的更換或動態的指定?


別急,透過下面舉的範例,會從中為使用者一一解答!


範例: 開發一個模擬鴨子行為的軟體

開發一個軟體前,使用者要想到這個軟體的需求以方便設計軟體的功能。一個模擬鴨子行為的軟體,顧名思義就是要提供鴨子行為的功能,如游泳(swim)、叫(quack)以及呈現自我種類(display)等,如下圖一。

DP_Strategy.JPG  

這樣的架構看似合理與容易擴充,只要有一個鴨子(Duck)的父類別,並提供鴨子的行為功能,其他不論是什麼鴨子(如綠頭鴨(MallardDuck)或紅頭鴨(RedheadDuck))只要繼承(Is a)父類別就可以有這些行為,可自行定義這些行為。但事情真的這麼順利嗎? 如果…今天想加入另一個行為,如飛行(fly)呢? 恩,好像還行的通,對於要多加行為功能這點,繼承做的不差嘛。

 DP_Strategy2.JPG 

 

那,如果…想多一個種類的鴨子呢? 如橡膠鴨(RubberDuck),喔喔~~問題好像出現了!! 用繼承(Is a)是可以輕易的得到父類別所提供的所有行為功能,但…橡膠鴨似乎不需要飛行(fly)的行為,但它也必須概括承受,這似乎不太合理。

 DP_Strategy3.JPG 

 

天殺的,繼承(Is a)看似好用,但其實也是有一些限制,像是不論爸爸有什麼,不管你想不想要,你都得概括承受! 但,天無絕人之路,問題總是有方法解決。

這是使用者可能立即想到的解決問題的方法之一,如下圖。在鴨子(Duck)這個父類別中只放最基本,未來不論什麼鴨子都有可能擁有的行為功能。而比較特別的行為功能(quack, fly)就一一抽離出來,讓擁有此行為的子類別各自去繼承自己有的行為。

恩,這樣看似解決了問題,但卻又產生另一個問題,多重繼承的問題,就未來程式的擴充與維護方面都會因為過多的小類別而不意維護。

 DP_Strategy4.JPG 

 

截至目前為止,會不會越看越模糊呢? 這跟策略模式的關係是? 讓我們先從回答一些基本的問題。

  • 何謂演算法?

A: 演算法就是實踐一個目的的方法,所以一個行為的實踐都可以稱為一個演算法。

  • 何謂頻繁的更換或動態的指定?

A: 在釐清何謂頻繁的更換與動態的指定時,要先知道為什麼要動態的指定以及更換。這樣說很模糊,直接舉例說明會比較容易明白。如果…今天紅頭鴨(RedheadDuck)要多一個跳(jump)的行為,但是要把飛行(fly)的行為刪掉,那是否會有動態的指定的需求出現。 如果…今天飛行(fly)的行為不只一種呢? 如果有直線飛行、彎曲飛行、亂飛等不同方式呢? 紅頭鴨不想再用直線飛行了,想改亂飛呢? 那是否會有頻繁的更換的需求出現呢。其實,解釋到此,答案也出來了。

 

沒錯,上述的問與答其實也點出了一些隱性的問題! 例如,若是我希望鴨子的行為方式改變,怎麼辦? 若是我在後期預為飛行行為加上一個新的方式,那我使用到這個飛行行為的其他程式會因此受影響嗎? 要解決這個問題,就是要使用策略模式囉,讓我們看看到底如何使用策略模式解決這個問題!!

 

 

溫故知新,讓我們先重新複習一下策略模式的定義,在對它抽絲剝繭,了解每字每句的意義。首先是第一句,”定義了演算法家族,個別封裝起來”,還記得嗎,這也是我們先前提出的問題之一。

Q:何謂演算法家族,何謂個別封裝起來?

A: 之前定義過何謂演算法,演算法就是實踐一個目的的方法,就像鴨子中的飛行行為。顧名思義,演算法家族就是一堆演算法的集合,先前提到飛行可能會有不同的飛行方式,這些所謂的不同的方式,就是很多個不同的演算法,而將一個演算法當成一個類別,就是所謂的個別封裝,但要切記這些演算法都會實踐同一個抽象介面(FlyBehavior) 這個介面提供了共同的功能。所以將不同的飛行方式的演算法集合起來就是一個飛行演算法家族。

DP_Strategy5.JPG  

 

從下圖右上可以看到一個Duck class會使用一個飛行的演算法家族(Flying Behavior)與一個叫的演算法家族(Quacking Behavior),這兩個家族被封裝了起來。

DP_Strategy6.JPG  

 

第二句,”讓它們之間可以互相替換,此模式讓演算法的變動不會影響到使用演算法的程式”, 這句話是什麼意思呢? 其實就是指少用繼承(Is a),多用合成(Has a),鴨子(Duck)類別中有兩個演算法家族的參考(Reference),可用來動態的產生所需的飛行行為(FlyBehavior)以及叫行為(QuackBehavior)的方式(演算法),可以從下圖看到。因為是使用合成,演算法家族的參考,所以可以動態的產生,也可以動態的替換,除了動態這個好處,當某一個家族中要加入另一個新的方式時,在使用此參考的程式中也不會需要變動與更改,因為這些方式(演算法)都是繼承了同一個抽象介面。

 DP_Strategy7.JPG 

 

 

[Note]: 魚與熊掌不可兼得,當然策略模式也是有其限制,就是每一個演算法家族要提供一個抽象介面,所以對功能函式(function)的提供就不能是客製化的了,所有演算法都必須要實踐這個抽象介面所提供的功能才可以,而要去統整出一個適當的介面,能提供所有演算法都適用,要考慮的很周全。

Strategy在遊戲中的應用非常多,最常見的就是Shader。Shader就是一個很簡單的stragegy模式,本身就是封裝了一套演算法,每個model都對應到一個material,而且有很多的material可以動態的抽換,來對model產生不同的效果。

另外,射擊遊戲中的彈幕也可以用Strategy,可以寫出各種不同等級的彈幕,子機可以在吃到火力增強的道具時由一發子彈變成散彈的形式,而且不需要透過if else的判斷,直接抽換掉原本一發子彈換成三發子彈的strategy。

實例實在太多了。記得,當你在程式碼中發現if else或是switch case中要直行的部分有相同的行為或是介面,只是function名稱不同,那麼請試著將其改為Strategy,讓程式碼更容易維護與閱讀,避免繁複的if else或是switch case也可以多少提升一下速度(當這麼if else是透過迴圈呼叫時)

 

#所有圖片剪輯自Head First Design Pattern一書

全站熱搜
創作者介紹
創作者 kgsprogrammer 的頭像
kgsprogrammer

太陽系後援會

kgsprogrammer 發表在 痞客邦 留言(0) 人氣()