# 程式設計 ###### tags: `程式設計` ## Design Pattern - Strategy https://www.jyt0532.com/2017/09/12/template/ https://skyyen999.gitbooks.io/-study-design-pattern-in-java/content/strategy.html Inheritance 繼承 Interface -> implements #### 原本的設計: 有紅頭鴨與綠頭鴨inheritance鴨子。 但多了一隻鴨:黃色小鴨,繼承時,因為他不會叫,所以要override quack(),讓他不會叫。 但這只是其中一個例子,若不會叫的鴨某天多過會叫的鴨,那此程式就須大改...設計得不好。 ![](https://i.imgur.com/iyCAuYH.png) #### 開始改良: - Design Pattern rule #1: Encapsulate what varies 把所有可能會更動的地方獨立出來 不要跟不會變動的地方混在一起。 - Design Pattern rule #2: Program to an interface, not an implementation 把會變動的method改成interface去寫 再由interface的subclass去實作method 現在就有兩個interface: #### 改良後: ```java //第一個interface 不一定每隻鴨都會叫 interface QuackBehavior { void quack(); } //交給interface的subclass去實作quack的方法 class Quack implements QuackBehavior{ @Override void quack() { System.out.println("qua~ qua~"); } } class Mute implements QuackBehavior{ @Override void quack() { System.out.println("..."); } } //第二個interface 不一定每隻鴨都會飛 interface FlyBehavior { void fly(); } class FlyWithWings implements FlyBehavior{ void fly() { System.out.println("我飛我飛我飛飛飛"); } } class FlyNoWay implements FlyBehavior{ void fly() { System.out.println("我的翅膀不是長來飛的"); } } //重頭戲Duck class Duck { private QuackBehavior quackBehavior; private FlyBehavior flyBehavior; public setQuackBehavior(QuackBehavior q) { quackBehavior = q; } public setFlyBehavior(FlyBehavior f) { flyBehavior = f; } public void swim() { System.out.println("游R游RR"); } public void display() { System.out.println("I'm a real duck"); } public void performQuack() { quackBehavior.quack(); } public void performFly() { flyBehavior.fly(); } } //最後要繼承Duck時,只要分別在建構式宣告兩個interface即可有各種鴨的特性(飛/不飛、叫/不叫) class RedheadDuck extends Duck{ public RedheadDuck() { quackBehavior = new Quack(); flyBehavior = new FlyWithWings(); } //因為RedHead和Duck只有這一個method做的事不一樣,所以override它一個就好 public void display() { System.out.println("I'm a real Redhead duck"); } } ``` ![](https://i.imgur.com/nbrwT8a.png) #### 還可以改鴨子的飛行方式,且不會影響到其他種類的鴨子 ```java RedheadDuck rh = new RedheadDuck(); rh.performFly(); // FlyWithWings的fly實作 rh.setFlyBehavior(new FlyNoWay()); rh.performFly(); // FlyNoWay的fly實作 ``` ### Strategy pattern * **定義演算法家族 並把每個演算法封裝起來 演算法之間彼此可以互換** * **這個模式讓演算法的變動 不影響演算法的使用方式** * **把抽象的方法抽離在interface 把實作留給interface的subclass** #### Strategy 結構: ![](https://i.imgur.com/0YebuiR.png) #### 優點 1. Strategy可以針對同一種行為有不同的實作 1. Strategy interface還可以玩自己的hierarchy遊戲 繼承的機制可以把共有的功能提出來 1. 對於Context來說 每個subclass有他自己的演算法(可能和其他人重複 可能不重複) 如果把演算法實作寫在Context裡會很難maintain #### 缺點 1. Context必須對Strategy的實作清楚 才知道要挑哪一種演算法來用 1. Strategy和Cotext的通訊負擔變重 因為不論ConcreteStrategy裡實作起來簡單還是複雜 都是共用Strategy介面 Strategy介面的某些東西 可能一個簡單的ConcreteStrategy根本不需要 或是Context給它的初始化參數它用不到 這種情況也許你不該用Strategy pattern ## Design Pattern - Template #### 最初的設計: 泡茶的步驟如下: 1.把水煮開 2.沸水倒進茶葉 3.茶倒進杯子 4.加檸檬 泡咖啡的步驟如下: 1.把水煮開 2.用沸水沖咖啡 3.咖啡倒進杯子 4.加糖和牛奶 發現step.1和3根本一樣 #### 改良後: ```java //用抽象類別!! public abstract class CaffeineBeverage { final void prepareRecipe() { boilWater(); brew(); pourInCup(); addCondiments(); } abstract void brew(); //步驟2 steepTeaBag, brewCoffeeGrinds abstract void addCondiments(); //步驟4 addLemon addSugarAndMilk void boilWater() {//步驟1 System.out.println("Boiling water"); } void pourInCup() {//步驟3 System.out.println("Pouring into cup"); } } //Tea只要override brew()和addCondiments()即可 class Tea extends CaffeineBeverage { public void brew() { System.out.println("Steeping the tea"); } public void addCondiments() { System.out.println("Adding Lemon"); } } //Coffee同上 class Coffee extends CaffeineBeverage { public void brew() { System.out.println("Dripping Coffee through filter"); } public void addCondiments() { System.out.println("Adding Sugar and Milk"); } } ``` 如果今天又要泡牛奶,牛奶不用加料addCondiments() 那可在prepareRecipe加上hook,去控制是否要加料 ```java public abstract class CaffeineBeverage { final void prepareRecipe() { boilWater(); brew(); pourInCup(); //新增這個 if (wantsCondiments()){ addCondiments(); } } ...省略 //新增這個 boolean wantsCondiments() { return true; } } class Milk extends CaffeineBeverage { public void brew() { System.out.println("Dripping Coffee through filter"); } //因為是抽象方法,所以一定要實作,把它改成啥都沒差,因為wantsCondiments會控制不加料 public void addCondiments() { } public boolean wantsCondiments() { return false; } } ``` #### Q: 奇怪你這不是多此一舉 你的牛奶直接implement addCondiments讓他不做任何事不就好了嗎 A: 那是剛好現在這個例子裡 addCondiments函數是抽象函數(父類別希望你實作的函數) 如果今天去if block裡面的是boilWater或是pourInCup 那就不能像你那樣搞 #### Q: 在你牛奶的case 你又override抽象函數 又override掛鉤 那我身為抽象父類別 到底該什麼時候用抽象函數 什麼時候用掛勾呢 A: 當你希望子類別一定要實作的函數 就用抽象函數 可以選擇要不要實作的函數 就用掛鉤 #### Tempelete method 結構 將一個演算法的骨架定義在一個方法中 而演算法本身用到的(一部分的)方法則定義在次類別中 Tempelete method讓次類別在不改變演算法骨架的前提下 重新定義演算法中的某些步驟 ![](https://i.imgur.com/HLooDlV.png) #### 優缺點 1. 由父類別主導演算法 而不是各個subclass各自maintain 2. 程式碼再利用 3. 演算法只存在一個地方 ### 差異 #### Strategy 讓你能夠在run time對於同一個function選擇完全不同的策略(演算法) 不同的策略之間彼此獨立不相關 (要選擇怎麼fly,可以立馬呼叫setFlyBehavior做修改,改完後,一樣呼叫performfly()就可飛了,但會依照新的Behavior飛) #### Template method 預先定義好了演算法的每個步驟 某些步驟是固定的 某些步驟是彈性的 彈性的步驟交給subclass去實作 (泡咖啡的步驟都在prepare定好了,brew和addCondiment和wantsCondiments都由subclass實作) ## 4 簡述Template、Strategy pattern,並說明他們之間的差異 #### Template: 以抽象類別為基礎,把演算法都放在一個方法中,讓次類別在不改動方法的情況下重新定義某部分的步驟。 ```java= class abstract Teacher { final void do() { work(); if (isFullTimeTeacher()) { //hook calculateFullTimeSalary(); } else { calculatePartTimeSalary(); } calculateEfficiency(); } void work() { System.out.println("do work"); } void calculateFullTimeSalary(int month) { System.out.println(String.valueOf(month*40000)); } void calculatePartTimeSalary(int wage ,int hour) { System.out.println(String.valueOf(wage*hour)); } boolean isFullTimeTeacher() { return true; } abstract void calculateEfficiency(); } class FullTimeTeacher extends Teacher { public void calculateEfficiency() { System.out.println("calculate full-time teacher's efficiency."); } } ``` #### Strategy pattern: 定義父類別時,將子類別不一定會使用到的method放入interface,再交由subclass實作。父類別內就以interface的方式增加彈性,開放各種子類別選擇的空間。沿用上面DIP的例子,當有另一class繼承Teacher,可任意選擇managable的權限,且不用改動原本的程式碼。 ```java= Class PartTimeTeacher extends Teacher{ PartTimeTeacher() { Managable = new Score(); } } ``` #### 差異: Template是已經定義好演算法的步驟,若某步驟有不同的選擇,那就交由subclass去定義。 Strategy可以讓我們在run time上選擇不同的策略(setManagable),呼叫同一個method時(managed)出現不同的效果。 <style> h2{ color: #65A287; } h4{ color: #875244; } li:first-line{ color: #6699BA; } </style>