設計模式之模板方法模式(二)
- 2019 年 12 月 26 日
- 筆記
上一篇我們已經學會了模板方法模式,這次我們繼續來深入學習下。畢竟學會只是第一步,還有更進一步的學習和深入等著我們呢。
我們先來看下,對模板方法模式的一個總結的類圖:

讓我們細看抽象類是如何被定義的,包含了它內含的模板方法和原語操作。
abstract class AbstractClass { // 這就是模板方法。它被聲明為final,以免子類改變這個演算法的順序 final void templateMethod() { // 模板方法定義了一連串的步驟,每個步驟由一個方法代表 primitiveOperation1(); primitiveOperation2(); concreteOperation(); hook(); } abstract void primitiveOperation1(); abstract void primitiveOperation2(); final void concreteOperation() { // 這裡是實現 } // 這是一個具體的方法,但他什麼都不做。我們叫它為hook(鉤子),馬上就來揭曉它如何使用 void hook(); }
對模板方法進行掛鉤
鉤子是一種被聲明在抽象類中的方法,但只有空的或者默認的實現。鉤子的存在,可以讓子類有能力對演算法的不同點進行掛鉤。要不要掛鉤,由子類決定。
鉤子有很多用途,我們先看其中一個:
public abstract class CaffeineBeverageWithHook { final void prepareRecipe() { boilWater(); brew(); pourInCup(); // 我們加上一個小小的條件語句,該條件是否成立,是由一個具體方法決定 if (customerWantsCondiments()) { addCondiments(); } } abstract void brew(); abstract void addCondiments(); void boilWater() { System.out.println("Boiling water"); } void pourInCup() { System.out.println("Pouring into cup"); } // 這就是一個鉤子,子類可以覆蓋這個方法,但不一定需要使用 boolean customerWantsCondiments() { return true; } }
為了使用鉤子,我們在子類中覆蓋它。在這裡,鉤子控制了咖啡因飲料是否執行某部分演算法;比如,飲料中是否需要加進調料
public class CoffeeWithHook extends CaffeineBeverageWithHook { public void brew() { System.out.println("Dripping Coffee through filter"); } public void addCondiments() { System.out.println("Adding Sugar and Milk"); } public boolean customerWantsCondiments() { // 詢問用戶,是否要加調料 String answer = getUserInput(); if (answer.toLowerCase().startsWith("y")) { return true; } else { return false; } } private String getUserInput() { String answer = null; System.out.print("Would you like milk and sugar with your coffee (y/n)? "); BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); try { answer = in.readLine(); } catch (IOException ioe) { System.err.println("IO error trying to read your answer"); } if (answer == null) { return "no"; } return answer; } }
上面就是一個鉤子,詢問顧客是否想要調料?如果需要,就執行內容,否則就不執行。測試程式碼如下:
public class BeverageTestDrive { public static void main(String[] args) { Tea tea = new Tea(); Coffee coffee = new Coffee(); System.out.println("nMaking tea..."); tea.prepareRecipe(); System.out.println("nMaking coffee..."); coffee.prepareRecipe(); TeaWithHook teaHook = new TeaWithHook(); CoffeeWithHook coffeeHook = new CoffeeWithHook(); System.out.println("nMaking tea..."); teaHook.prepareRecipe(); System.out.println("nMaking coffee..."); coffeeHook.prepareRecipe(); } }
執行結果如下:茶要加調料就給你加上了;咖啡不需要加調料,就沒給你加
Making tea... Boiling water Steeping the tea Pouring into cup Would you like lemon with your tea (y/n)? y Adding Lemon Making coffee... Boiling water Dripping Coffee through filter Pouring into cup Would you like milk and sugar with your coffee (y/n)? n
那麼,我們使用鉤子的真正目的是什麼呢?
鉤子有幾種用法。如我們之前所說的,鉤子可以讓子類實現演算法中可選的部分,或者在鉤子對於子類的實現並不重要的時候,子類可以對此鉤子置之不理。鉤子的另一個用法,是讓子類能夠 有機會對模板方法中某些即將發生的(或剛剛發生的)步驟做出反應。比方說,名為justReOrderedList()的鉤子方法允許子類在內部列表重新組織後執行某些動作(例如在螢幕上重新顯示數據)。正如你剛剛看到的,鉤子也可以讓子類有能力為其抽象類做一些決定。
好萊塢原則
好萊塢原則:別調用(打電話給)我們,我們會調用(打電話給)你。
好萊塢原則可以給我們一種防止「依賴腐敗」的方法。當高層組件依賴低層組件,而低層組件又依賴高層組件,而高層組件又依賴邊側組件,而邊側組件又依賴低層組件時,依賴腐敗就發生了。在這種情況下,沒有人可以輕易地搞懂系統是如何設計的。
在好萊塢原則下,我們允許低層組件將自己掛鉤到系統上,但是高層組件會決定什麼時候和怎樣使用這些低層組件。換句話說,高層組件對待低層組件的方式是「別調用我們,我們會調用你」。
好萊塢原則和模板方法之間的連接其實還算明顯:當我們設計模板方法時,我們告訴子類「不要調用我們,我們會調用你」。怎樣才能辦到呢?讓我們再看一次咖啡因飲料的設計:

我們之前還知道一個原則叫依賴倒置原則,好萊塢原則也是有點這個味道的對吧。他們之間的關係是如何的呢?
依賴倒置原則教我們盡量避免使用具體類,而多實用抽象。而好萊塢原則是用在創建框架或組件上的一種技巧,好讓低層組件能夠被掛鉤進計算中,而且又不會讓高層組件依賴低層組件。兩者的目標都是在於解耦,但是以來倒置原則更加註重如何在設計中避免依賴。
好萊塢原則教我們一個技巧,創建一個有彈性的設計,允許低層結構能夠互相操作,而又防止其他類太過於依賴它們。
這樣我們就把開篇說的隱藏的原則給介紹完了,也更進一步的知道了模板方法模式鉤子的用法,讓我們在實戰中能有一個更好的選擇。這個設計模式,你get到了嗎?
小編本來想在這完結的,但是看了下書,發現後面還有一個更貼近實際的,意想不到的模板方法,而且是我們平時會使用到的,我們下篇來聊聊。
愛生活,愛學習,愛感悟,愛挨踢