<h1>首先我們</h1>先來看兩個例子:沖咖啡和泡茶。沖咖啡和泡茶的基本流程如下:
所以用代碼來創建如下:
咖啡:Caffee.java
1 public class Coffee { 2 void prepareRecipe(){ 3 boilWater(); 4 brewCoffeeGrinds(); 5 pourInCup(); 6 addSugarAndMilk(); 7 } 8 9 void boilWater(){ 10 System.out.println("Boiling water..."); 11 } 12 13 public void brewCoffeeGrinds(){ 14 System.out.println("Dripping Coffee through filter..."); 15 } 16 17 18 void pourInCup(){ 19 System.out.println("Pouring into Cup..."); 20 } 21 22 public void addSugarAndMilk(){ 23 System.out.println("Adding Sugar and Milk..."); 24 } 25 }
茶:Tea.java
1 public class Tea { 2 void prepareRecipe(){ 3 boilWater(); 4 steepTeaBag(); 5 pourInCup(); 6 addLemon(); 7 } 8 9 void boilWater(){ 10 System.out.println("Boiling water..."); 11 } 12 13 public void steepTeaBag(){ 14 System.out.println("Steeping the tea..."); 15 } 16 17 18 void pourInCup(){ 19 System.out.println("Pouring into Cup..."); 20 } 21 22 public void addLemon(){ 23 System.out.println("Adding Lemon..."); 24 } 25 }
通過上面兩個類的實現我們發現一些重復的代碼。從一開始接觸設計模式,我們就知道這樣一個設計原則:將用中需要變化的部分取出並“封裝”起來,即我們需要將某些相同代碼抽象出來。根據這個原則我們可以確認這個應用中存在兩個不變的部分:把水煮沸、把茶倒入杯子中,需要變化的有:用沸水沖泡咖啡(茶)、加入糖和牛奶(加入檸檬)。所以這里需要將這個變化部分抽象出來,交由子類去實現。
我們再仔細看看這兩個步驟還有什么相同之處呢?這里我們發現用沸水沖泡咖啡和泡茶葉存在一個共同點,那就是用沸水沖泡,只不過沖泡的對象不同罷了,加入糖和牛奶(檸檬)同樣的道理,都是加入調料,只不過加入的調料不同而已。所以這里可以將prepareRecipe()方法做如下修改:
做了上面的一些修改,我們可以看出泡咖啡和泡茶將共用一個相同的泡法(算法):
把水煮沸——>用沸水沖泡——>倒入杯子中——>加入調料。
通過上面的一些簡要的介紹,對模板方法模式有了一個初步的認識。那么什么是模板方法模式呢?
一、模式定義
所謂模板方法模式就是在一個方法中定義一個算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變算法結構的情況下,重新定義算法中的某些步驟。
模板方法模式是基於繼承的代碼復用技術的。在模板方法模式中,我們可以將相同部分的代碼放在父類中,而將不同的代碼放入不同的子類中。也就是說我們需要聲明一個抽象的父類,將部分邏輯以具體方法以及具體構造函數的形式實現,然后聲明一些抽象方法讓子類來實現剩余的邏輯,不同的子類可以以不同的方式來實現這些邏輯。
其實所謂模板就是一個方法,這個方法將算法的實現定義成了一組步驟,其中任何步驟都是可以抽象的,交由子類來負責實現。這樣就可以保證算法的結構保持不變,同時由子類提供部分實現。
模板是一個方法,那么他與普通的方法存在什么不同呢?模板方法是定義在抽象類中,把基本操作方法組合在一起形成一個總算法或者一組步驟的方法。而普通的方法是實現各個步驟的方法,我們可以認為普通方法是模板方法的一個組成部分。
二、模式結構
從上面的結構可以看出,模板方法模式就兩個角色:
AbstractClass: 抽象類
ConcreteClass: 具體子類
其中抽象類提供一組算法和部分邏輯的實現,具體子類實現剩余邏輯。
三、模式實現
使用上面的泡咖啡和泡茶。
首先是抽象類,該抽象類提供了沖泡咖啡或者茶的具體流程,並且實現了邏輯步驟,煮沸水和倒入杯子中。將用沸水沖泡和加入調料交由具體的子類(咖啡、茶)來實現。
1 public abstract class CaffeineBeverage { 2 3 /** 4 * 5 * @desc 6 * 模板方法,用來控制泡茶與沖咖啡的流程 7 * 申明為final,不希望子類覆蓋這個方法,防止更改流程的執行順序 8 * @return void 9 */ 10 final void prepareRecipe(){ 11 boilWater(); 12 brew(); 13 pourInCup(); 14 addCondiments(); 15 } 16 17 /** 18 * @desc 19 * 將brew()、addCondiment()聲明為抽象類,具體操作由子類實現 20 * @return void 21 */ 22 abstract void brew(); 23 24 abstract void addCondiments(); 25 26 void boilWater(){ 27 System.out.println("Boiling water..."); 28 } 29 30 void pourInCup(){ 31 System.out.println("Pouring into Cup..."); 32 } 33 }
然后是具體的子類實現:
Coffee.java
1 public class Coffee extends CaffeineBeverage{ 2 3 void addCondiments() { 4 System.out.println("Adding Sugar and Milk..."); 5 } 6 7 void brew() { 8 System.out.println("Dripping Coffee through filter..."); 9 } 10 11 }
Tea.java
1 public class Tea extends CaffeineBeverage{ 2 3 void addCondiments() { 4 System.out.println("Adding Lemon..."); 5 6 } 7 8 void brew() { 9 System.out.println("Steeping the tea..."); 10 } 11 12 }
完成,做了這么久終於可以泡杯咖啡來喝了。
1 public class Test { 2 public static void main(String[] args) { 3 Tea tea = new Tea(); 4 tea.prepareRecipe(); 5 } 6 }
從上面的運行結果可以看出,我們的模板方法模式表現的非常良好,但是我們似乎忽略了一些東西?如果某些客戶並不喜歡加入調料,而喜歡原生態的,但是我們的算法總是會給客戶加入調料,怎么解決?
遇到這個問題我們可以使用鈎子。所謂鈎子就是一種被聲明在抽象類中的方法,但只有空的或者默認的實現。鈎子的存在可以使子類能夠對算法的不同點進行掛鈎,即讓子類能夠對模板方法中某些即將發生變化的步驟做出相應的反應。當然要不要掛鈎,由子類決定。
所以對於上面的要求,我們可以做出如下修改。
1 public abstract class CaffeineBeverageWithHook { 2 3 void prepareRecipe(){ 4 boilWater(); 5 brew(); 6 pourInCup(); 7 if(customerWantsCondiments()){ //如果顧客需要添加調料,我們才會調用addCondiments()方法 8 addCondiments(); 9 } 10 } 11 12 abstract void brew(); 13 14 abstract void addCondiments(); 15 16 void boilWater(){ 17 System.out.println("Boiling water..."); 18 } 19 20 void pourInCup(){ 21 System.out.println("Pouring into Cup..."); 22 } 23 24 public boolean customerWantsCondiments(){ 25 return true; 26 } 27 }
客戶是否需要加入調料,只需要回答y或者n
1 public class CoffeeWithHook extends CaffeineBeverageWithHook{ 2 void addCondiments() { 3 System.out.println("Adding Sugar and Milk..."); 4 } 5 6 void brew() { 7 System.out.println("Dripping Coffee through filter..."); 8 } 9 10 /** 11 * 覆蓋該鈎子,提供自己的實現方法 12 */ 13 public boolean customerWantsCondiments(){ 14 if("y".equals(getUserInput().toLowerCase())){ 15 return true; 16 } 17 else{ 18 return false; 19 } 20 } 21 22 public String getUserInput(){ 23 String answer = null; 24 System.out.print("Would you like milk and sugar with your coffee(y/n):"); 25 BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 26 try { 27 answer = in.readLine(); 28 } catch (IOException e) { 29 e.printStackTrace(); 30 } 31 if(answer == null){ 32 return "n"; 33 } 34 return answer; 35 36 } 37 }
測試程序
1 public class Test { 2 public static void main(String[] args) { 3 CoffeeWithHook coffeeHook = new CoffeeWithHook(); 4 coffeeHook.prepareRecipe(); 5 } 6 }
運行結果
從上面可以看出鈎子能夠作為條件來進行控制。
四、模式優缺點
優點
1、模板方法模式在定義了一組算法,將具體的實現交由子類負責。
2、模板方法模式是一種代碼復用的基本技術。
3、模板方法模式導致一種反向的控制結構,通過一個父類調用其子類的操作,通過對子類的擴展增加新的行為,符合“開閉原則”。
缺點
每一個不同的實現都需要一個子類來實現,導致類的個數增加,是的系統更加龐大。
五、使用場景
1、 一次性實現一個算法的不變的部分,並將可變的行為留給子類來實現。
2、 各子類中公共的行為應被提取出來並集中到一個公共父類中以避免代碼重復。
3、控制子類的擴展。
六、模式總結
1、 模板方法模式定義了算法的步驟,將這些步驟的實現延遲到了子類。
2、 模板方法模式為我們提供了一種代碼復用的重要技巧。
3、 模板方法模式的抽象類可以定義抽象方法、具體方法和鈎子。
4、 為了防止子類改變算法的實現步驟,我們可以將模板方法聲明為final。