--- tags: Design Pattern,Creational Pattern title: Strategy (1) --- {%hackmd Sy5Quc5JY %} # <font color="#F59F00">**Strategy (1)**</font> >**Design Pattern rule #0 : In software, the requirments always change.** >**軟體開發的世界裡,需求會一直在改變。** >**Design Pattern rule #1 : Encapsulate what varies.** >**把所有可能會更動的地方獨立出來 不要跟不會變動的地方混在一起。** >**Design Pattern rule #2 : Program to an interface, not an implementation.** >**我們應該依賴可變的虛擬,而不是固定的實作。** >**Design Pattern rule #3 : More Has-a, less ls-a.** >**多用合成,少用繼承。** ## <font color="#F59F00">**Inheritnce**</font> Inheritance 是一個很好 reuse code 的方式。 * **Example** 紅頭鴨 `RedheadDuck` 、和綠頭鴨 `MallardDuck` ,我們不需要寫兩個想同的方法 `swim` ,我們只要寫一個 `Duck` 去繼承它就好。<br> <span style="display:block;text-align:center;"><img src="https://www.jyt0532.com/public/strategy1.png" width="400" height="320"></img></span> `Display` 是一個 abstract method,每個繼承 `Duck` 的 class 都會執行。 * <font color="#92d1ff">**Problems & Resolution**</font> Q : 如果今天來了個黃色小鴨? A : <span style="display:block;text-align:center;"><img src="https://www.jyt0532.com/public/strategy2.png" width="400" height="320"></img></span> 橡皮鴨雖然不會叫,但只要 override `quack` 就好。 Q : 根據 ==**rule #0**== ,今天突然要求要在 `Duck` 裡面加 `fly` 這個方法。 A : 為了 reuse `fly` 這個方法,所有會飛的鴨子不用重寫,但是不會飛的鴨子就一一 override, Q : 但其實這又是一個問題,對於不會飛的鴨子來說,`fly` 這個方法也是 duplicate code,且如果最後不會飛的鴨子比較多,我們難道反而要把 `fly` 改成 `cant_fly`? Q : 更重要的問題,compile time 是寫死的,如果今天在程式運作時鴨子突然會飛了。要從 `cant_fly` 變成 `fly` 呢? A : 跟據 ==**rule #1**== ,那我們可以使用 Interface 把會改動的功能分開,會使用再把它實作就好啦,也就是下圖。 ## <font color="#F59F00">**Interface**</font> * **Example** <br> <span style="display:block;text-align:center;"><img src="https://www.jyt0532.com/public/strategy3.png" width="550" height="280"></img></span> 虛線是實作,實線是繼承。 DecoyDuck 不會飛也不會叫,所以都不用 implement ,要是要再新增一個 `walk` 直接實作它就好了。 * <font color="#92d1ff">**Problems & Resolution**</font> Q : 你有發現一件事,這個 code 的 reuse 想當的爛,所有會 `fly` 的方法都要重寫一遍,如果今天 `fly` 要改,那全部的 `fly` 都要改,而且 compile time 寫死的問題沒有解決,那要怎麼改? A : 跟據 ==**rule #1、rule #2**==,我們把動作 `fly` `quack` 分開,用一個 Interface 並用各自的 SubClass 去實作,而不是在 Duck 的 SubClass。 <br> <span style="display:block;text-align:center;"><img src="https://www.jyt0532.com/public/strategy4.png" width="500" height="200"></img></span> `QuackBehavior` `FlyBehavior` 兩個 interface 各自有各自的 subclass 去 implement。<br> <span style="display:block;text-align:center;"><img src="https://www.jyt0532.com/public/strategy5.png" width="750" height="400"></img></span><br> ```java interface QuackBehavior { public void quack(); } interface FlyBehavior { public void fly(); } class Quack extends QuackBehavior { //Quack //quack(); } class Squack extends QuackBehavior { //Squack //quack(); } class Mute extends QuackBehavior { //Mute //fly(); } class FlyWithWings extends FlyBehavior { //FlyWithWings //fly(); } class FlyNoWay extends FlyBehavior { //FlyNoWay //quack(); } interface Duck { FlyBehavior flyBehavior; QuackBehavior quackBehavior; public void setFlyBehavior(FlyBehavior flyBehavior) { //... } public void performFly() { //... } public void setQuackBehavior(QuackBehavior quackBehavior) { //... } public void performQuack() { //... } public void swim() { //... } } class MallardDuck extends Duck { public MallarDuck() { quackBehavior = new Quack(); fiyBehavior = new flyWithWings(); } public void display() { System.out.println("isMallardDuck"); } } //... ``` 在每個 `Duck` 的 subclass 只要事先在 construct 裡面先 new 好一個 instance 就好了,而且因為有 `setFlyBehavior` 我可以在 compile time 時決定它什麼時候會飛,如果有新的飛行方式,去重新 implement `flyBehavior` 就好,不需要改變其他 client 的用法。 一隻鴨子的實作 <br> ```java MallardDuck mallard = new MallarDuck(); //FlyWithWings 的實作 mallardDuck.performFly(); //FlyWithRockets 的實作 mallardDuck.setFlyBehavior(new FlyWithRockets()); mallardDuck.performFly(); //Quack 的實作 mallardDuck.performQuack(); //Squack 的實作 mallardDuck.setQuackBehavior(new Squack()); mallardDuck.performQuack(); ``` ## <font color="#F59F00">**Strategy Pattern**</font> **定義演算法家族(interface),並把每個演算法(concrete impl)封裝起來,演算法之間可以彼此互換,這個的模式,讓演算法的變動不影響演算法的使用方式,把抽象的方法用 interface 抽離,把實作留給 interface 的 subclass** * <font color="#92d1ff">**Strategy Pattern 的結構**</font><br> <span style="display:block;text-align:center;"><img src="https://www.jyt0532.com/public/strategy6.png" width="580" height="200"></img></span><br> * <font color="#98c37a">**Context : Duck**</font> 用 ConcreteStrategy 的物件,來設定狀態。 <br> ```java flyBehavior = new FlyWithWings(); ``` * <font color="#98c37a">**Strategy : FlyBehavior**</font> 制定所有演算法的共同介面 Context 透過這個 interface 呼叫 ConcreteStrategy 實作的演算法。 <br> ```java flyBehavior.fly(); ``` * <font color="#98c37a">**ConcreteStratrgy : FlyWithWings**</font> 跟據 Strategy 的 interface 實作演算法。 * <font color="#92d1ff">**Conclusion**</font> * **Example** 根據rule #3,鴨子的行為不是繼承而來,而是由其他行為物件組成。 * <font color="#98c37a">**Pros**</font> 1. Strategy 可以針對同一種行為有不同的實作。 2. Strategy interface 還可以 hierarchy , 繼承共同的功能。 3. 對於 Context 來說,每個 subclass 有它自己的演算法,可以重複,可以不重複,所以如果把實作寫在 Context 很難 maintain * <font color="#98c37a">**Cons**</font> 1. Context 必須先對 Strategy 的實作清楚,才知道要挑選哪一個演算法作使用。 2. Strategy 和 Context 的通訊負擔變重,因為不論 ConcreteStrategy 裡面的實作是簡單還是複雜,都是共用 Strategy 介面的東西,可能一個簡單的 ConcreteStrategy 根本用不到那麼多,或是初始化的參數根本用不到,那你根本就不該用 Strategy Pattern。 * <font color="#98c37a">**Application**</font> **1. 定義所有的可以互相交換的演算法共同的 interface** **2. 把演算法的時做細節留給 subclass** **3. client 依賴 interface ,而不是具體實作**
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up