模板方法(Template method),顧名思義,就是作一些任務的通用流程。如網上有許多自我介紹模板、推薦信模板,即開頭和結尾可能都是差很少的內容,而中間須要客戶去修改一下便可使用。設計模式源自生活,模板方法就在相似的場景下誕生了。模板方法是指寫一個操做中的算法框架,而將一些步驟延遲到子類中去實現,這樣就使得子類能夠不改變一個算法的結構便可重定義該算法的某些特定步驟。 java
模板方法一般會設計一個抽象類,內部定義一些須要子類去實現的抽象方法,由於這些方法可能因不一樣的子類會有不一樣的實現,所以定義爲抽象方法。另外,抽象類中應該有一個或多個模板方法,即固定好的框架,並且這個方法的修飾符一般定義爲final,這樣作是爲了防止子類去覆寫這個模板,由於咱們認爲模板是固定的,不容修改。而在這個固定的模板方法內部,會調用那些抽象方法,來一步一步實現整個算法的流程。一般,子類要去繼承這個抽象父類,並根據本身的業務邏輯實現裏面的抽象方法。 算法
模板方法清晰地劃定了某一類業務的變與不變,爲一類業務作好了流程框架,爲子類提供了公共的代碼,而且子類的行爲徹底由父類來控制,實現了代碼的可維護性和可擴展性。父類不容修改,子類能夠去擴展,很好地符合了設計模式的開閉原則——對修改封閉,對擴展開放。 設計模式
注意模板方法設計模式與抽象類設計的區別,抽象類這種設計模式是父類定義一些抽象方法,讓子類去實現,所以子類一般有更多的自由空間;而模板方法中是父類定義好了算法框架,子類去實現父類其中的抽象方法,所以子類的做用能夠影響父類。 框架
假設如今要製做一些飲料產品,比方說要泡茶和咖啡。泡茶和泡咖啡的流程大致上能夠分爲四步,第一將水煮沸,第二烘焙原料,第三倒入杯中,第四加入調料。一般第一步和第三步動做是同樣的,因此咱們能夠在父類中將方法直接寫好,而第二步和第四步則隨着泡茶仍是泡咖啡有所不一樣,所以咱們設計爲抽象方法,讓子類去實現。而這四步總體上又是泡飲料的固定流程,因此咱們將這四步封裝在一個方法中,而且設置這個方法的修飾符爲final,以防子類去修改它。 ide
下面先寫出模板方法的代碼: 函數
package com.template; /** * 模板模式 * 抽象基類,爲全部子類提供算法框架 * 業務:提神飲料 * @author zzw * */ public abstract class RefreshBeverage { /* * 製備飲料的模板方法,指定算法框架 */ //阻止子類對模板方法進行復寫 public final void prepareBeverageTemplate() { //步驟1:將水煮沸 boilWater(); //步驟2:炮製飲料 brew(); //步驟3:倒入杯中 pourInCup(); //步驟4:加入調料(引入鉤子函數,從用戶角度出發,可選擇性) if(isCustomerWantCondiments()) { addCondiments(); } } /* * Hook,鉤子函數 * 提供一個默認或空的實現 * 具體子類能夠自行決定是否掛鉤以及如何掛鉤 * 詢問用戶是否加入調料 */ protected boolean isCustomerWantCondiments() { // 默認設置 return true; } //抽象方法,由子類實現 protected abstract void addCondiments(); private void pourInCup() { // 倒入杯中 System.out.println("倒入杯中"); } //抽象方法,由子類實現 protected abstract void brew(); private void boilWater() { // 將水煮沸 System.out.println("將水煮沸"); } }
因爲泡茶喝泡咖啡的具體實現有所不一樣,而且還可使用鉤子函數來判斷用戶是否須要加調料,這樣使得流程更具人性化。 spa
泡茶: 線程
package com.template; /** * 茶葉製備的具體實現 * 子類 * @author zzw * */ public class TeaBeverage extends RefreshBeverage { @Override protected void addCondiments() { // TODO Auto-generated method stub System.out.println("加入茶葉調料"); } @Override protected void brew() { // TODO Auto-generated method stub System.out.println("烘焙茶葉"); } }
package com.template; public class CoffeeBeverage extends RefreshBeverage { @Override protected void addCondiments() { // TODO Auto-generated method stub System.out.println("加入咖啡調料"); } @Override protected void brew() { // TODO Auto-generated method stub System.out.println("烘焙咖啡"); } }
package com.template; /** * 製備中式茶 * 不須要加調料 * 所以選擇掛鉤函數 * 其餘的則繼承茶的製做 * @author Administrator * */ public class ChineseTeaBeverage extends TeaBeverage { @Override protected boolean isCustomerWantCondiments() { // TODO Auto-generated method stub return false; } }
鉤子其實是一個處理消息的程序段,經過系統調用,把它掛入系統。每當特定的消息發出,在沒有到達目的窗口前,鉤子程序就先捕獲該消息,亦即鉤子函數先獲得控制權。這時鉤子函數便可以加工處理(改變)該消息,也能夠不做處理而繼續傳遞該消息,還能夠強制結束消息的傳遞。
對每種類型的鉤子由系統來維護一個鉤子鏈,最近安裝的鉤子放在鏈的開始,而最早安裝的鉤子放在最後,也就是後加入的先得到控制權。如本博文中提到的是否加入調料即爲一種鉤子函數,它對是否加調料具備決定權。
若是指定肯定的線程,即爲線程專用鉤子;若是指定爲空,即爲全局鉤子。其中,全局鉤子函數必須包含在DLL(動態連接庫)中,而線程專用鉤子還能夠包含在可執行文件中。獲得控制權的鉤子函數在完成對消息的處理後,若是想要該消息繼續傳遞,那麼它必須調用另一個SDK中的API函數CallNextHookEx來傳遞它。鉤子函數也能夠經過直接返回TRUE來丟棄該消息,並阻止該消息的傳遞。 設計
模板方法能夠用於一次性實現一個算法的不變的部分,並將可變的部分留給子類去實現;子類的公共代碼部分應該被提煉到父類中去寫好,防止代碼重複編寫;控制子類的擴展,模板方法只容許在特定點調用鉤子函數,這樣就只容許在這些點進行擴展。 code