《Head First设计模式》第八章笔记-模板方法模式
模板方法模式
之前所學習的模式都是圍繞著封裝進行,如對象創建、方法調用、復雜接口的封裝等,這次的模板方法模式將深入封裝算法塊,好讓子類可以在任何時候都將自己掛接進運算里。
模板方法定義:模板方法模式在一個方法中定義一個算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變算法結構的情況下,重新定義算法中的某些步驟。
模板方法就是一個固定步驟的“算法”骨架方法。這個算法的可變部分通過繼承,在子類中重載實現。這樣就可以在算法骨架不變的情況下,算法細節步驟根據不同的需求進行適應的改變。
例題:茶飲店的飲品沖泡程序(泡茶與泡咖啡)
| 1 2 3 4 5 6 | 泡茶:???????????????????????????????????????????? |??????????? 咖啡: ??????????????????????????????????????????????????| 1. 煮沸水????????????????????????????????????????? |??????????????1.煮沸水????????? 2. 加入茶葉沖泡???????????????????????????????????? |??????????????2.加入咖啡粉沖泡 3. 根據需求加入調料(如蜂蜜、檸檬)?????????????????? |??????????????3.根據需求加入調料(如牛奶、糖) 4. 將泡好的茶水倒入杯子????????????????????????????? |??????????????4.將泡好的咖啡倒入杯子 |
我們發現兩者的步驟非常相似,僅有部分細節不一:如泡茶沖的是茶葉,加的是蜂蜜;泡咖啡加的是牛奶其實泡茶和泡咖啡的過程就是一個固定骨架步驟的“算法”,我們可以抽象為:
斜體部分為算法中不一樣的部分,如何解決?下面,我們用“模板方法模式”來解決這種不一致。
首先,定義一個含有固定骨架“模板方法”的咖啡因飲料抽象類:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | /** ?*咖啡因飲料 ?*/ public abstract class CaffeineBeverage { ????/** ?????*模板方法,準備飲料 ?????*/ ????public final void prepareRecipe(){ ????????boilWater(); ????????brew(); ????????//用于模板方法的算法中可選部分的控制 ????????if(customerWantsCondiment()) ????????????addCondiment(); ????????pourInCup(); ????} ? ????/** ?????*煮沸水 ?????*/ ????public void boilWater() { ????????System.out.println("煮沸水"); ????} ? ????/** ????????*沖泡 ?????*/ ????public abstract void brew(); ? ????/** ?????*增加調味劑 ?????*/ ????public abstract void addCondiment(); ? ????/** ??????*將飲料倒入杯子 ?????*/ ????public void pourInCup() { ????????System.out.println("將飲料倒入杯中"); ????} ? ????/** ?????*“鉤子”方法。顧客決定是否加調料 ?????*/ ????public Boolean customerWantsCondiment(){ ????????return true; ????} } |
這時,準備飲料的四個固定步驟我們都寫在模板方法prepareRecipe()里了。這個算法步驟是不可更改的,所以我們給這個模板方法加了final關鍵字。
然后,根據茶和咖啡在算法步驟上的不同,我們設計兩個類,繼承抽象方法,分別重載模板方法中的步驟,從而實現茶和咖啡在算法步驟中各自的不同:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public class Tea?extends CaffeineBeverage{ ????@Override ????public void brew() { ????????System.out.println("浸泡茶葉"); ????} ? ????@Override ????public void addCondiment() { ????????System.out.println("添加蜂蜜"); ????} } ? public class Coffee?extends CaffeineBeverage{ ????@Override ????public void brew() { ????????System.out.println("沖泡咖啡粉"); ????} ? ????@Override ????public void addCondiment() { ????????System.out.println("添加糖和牛奶"); ????} } |
測試:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class Main { ????public static void main(String[] args) { ????????CaffeineBeverage tea =?new Tea(); ????????tea.prepareRecipe(); ????????System.out.println("==============="); ????????CaffeineBeverage coffee =?new Coffee(); ????????coffee.prepareRecipe(); ????} } /**輸出: ?煮沸水 ?浸泡茶葉 ?添加蜂蜜 ?將飲料倒入杯中 ?=============== ?煮沸水 ?沖泡咖啡粉 ?添加糖和牛奶 ?將飲料倒入杯中 */ |
可以看到,通過繼承,模板方法在茶和咖啡中的實現有了差別。
模板方法將算法定義成一組步驟,其中的任何步驟都可以是抽象的,由子類負責實現。這樣可以確保算法的結構保持不變,同時由子類提供部分的實現。
所以,模板方法就是定義了一個算法的步驟,并允許子類為一個或多個步驟提供實現。
最后,我們給出模板方法的類圖:
看上去模板方法似乎就這樣結束了,然而,在上面定義的抽象類中還有一個“鉤子(hook)”方法,
什么是“鉤子”方法呢?我們來看一下定義:
鉤子是一種被聲明在抽象類中的方法,但只有空的或者默認的實現。鉤子的存在,可以讓子類有能力對算法的不同點進行掛鉤。要不要掛鉤,由子類自行決定。
在上面的代碼中,我們寫了一個鉤子來決定是否加調料
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /** ?* “鉤子”方法。顧客決定是否加調料 ?*/ public Boolean customerWantsCondiment(){ ????return true; } /** ?* 模板方法,準備飲料 ?*/ public final void prepareRecipe(){ ????boilWater(); ????brew(); ????//用于模板方法的算法中可選部分的控制 ????if(customerWantsCondiment()) ????????addCondiment(); ????pourInCup(); } |
現在,我們用一個子類來掛鉤:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | public class Tea?extends CaffeineBeverage{ ????@Override ????public void brew() { ????????System.out.println("浸泡茶葉"); ????} ? ????@Override ????public void addCondiment() { ???????????System.out.println("添加蜂蜜"); ????} ? ????//覆蓋父類的“鉤子”方法,更改算法中的可選部分 ????@Override ????public Boolean customerWantsCondiment(){ ????????//詢問顧客是否需要調料 ????????String answer = askCustomerNeedCondiment(); ????????if("y".equals(answer)) ????????????return true; ????????else ????????????return false; ????} ? ????private String askCustomerNeedCondiment() { ????????String answer =?null; ????????System.out.println("請問您要不要加蜂蜜?請回答y或n"); ????????BufferedReader in =?new BufferedReader(new InputStreamReader(System.in)); ????????try { ????????????answer = in.readLine(); ????????}?catch (IOException e) { ????????????e.printStackTrace(); ????????} ????????return answer; ????} } |
這時,我們準備茶水時就能根據顧客的回答而安排需要加調料這一步驟了。子類通過覆蓋鉤子方法,實現了算法中的可選部分。
模板方法模式中還使用到了一個新的設計原則:好萊塢原則
好萊塢原則:別調用(打電話給)我們,我們會調用(打電話給)你
好萊塢原則可以給我們一種防止“依賴腐敗”的方法。當高層組件依賴低層組件,而低層組件又依賴高層組件,而高層組件又依賴邊側組件,而邊側組件又依賴低層組件時,依賴腐敗就發生了。在這種情況下,沒有人可以輕易地搞懂系統是如何設計的。
在好萊塢原則之下,我們允許低層組件將自己掛鉤到系統上,但是高層組件會決定什么時候和怎樣使用這些低層組件。換句話說,高層組件對待低層組件的方式是“別調用我們,我們會調用你”。
好萊塢原則就是確保不會出現高層組件依賴底層組件、底層組件又依賴高層組件的“依賴腐敗”。只有高層組件會決定什么時候和怎樣使用底層組件,而底層組件不會調用高層組件。
在模板方法模式中,算法的實現會調用到具體子類的某個方法,也就是高層組件依賴于底層組件。具體子類不會調用父類中的方法,不會形成底層組件依賴高層組件的環狀依賴:
采用好萊塢原則的設計模式還有:工廠方法(可以看作特殊的模板方法),觀察者、裝飾者...
好萊塢原則和依賴倒置原則的關系:依賴倒置原則是盡量避免使用具體類,多使用抽象。
策略模式與模板方法模式
策略模式和模板方法模式很像,都是針對算法改變的情況的設計模式。以下是它們的區別:
- 策略模式是采用的組合來實現算法的變化,這樣的設計更加靈活,依賴性程度低;
- 模板方法模式采用的繼承來實現算法中的變化部分,這樣的設計對算法有更多的控制權,且代碼的重復會少一些,但由于算法依賴于父類,所以依賴程度高。
Java API中的模板方法
Java中較常見的模板方法模式的應用:
注意,這個排序的例子表面看上去好像與模板方法模式無關(沒有用到繼承),但實質仍是通過子類提供算法步驟的實現來實現了算法的變化。雖然采用了組合,但思想仍是模板方法模式的思想。
總結
模板方法模式:在一個方法中定義一個算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變算法的結構情況下,重新定義算法中的某些步驟。
- 模板方法定義了算法的步驟,把這些步驟實現延遲到子類。
- 模板方法為我們提供了一種代碼復用的重要技巧。
- 模板方法的抽象類可以定義具體的方法、抽象方法和鉤子方法。
- 抽象方法由子類實現。
- 鉤子是一種方法,它在抽象類中不做事,或者只做默認的事,子類可以選擇要不要覆蓋它。
- 好萊塢原則告訴我們將決策權放在高層模板中,以便決定如何及何時調用底層模塊。
- 策略模式和模板方法模式都封裝算法,一個是用組合,一個是用繼承。
- 工廠方法是模板方法的一種特殊版本
總結
以上是生活随笔為你收集整理的《Head First设计模式》第八章笔记-模板方法模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: redis——Java整合
- 下一篇: 《Head First设计模式》第九章(