一、模板方法模式定義
模板方法模式:在一個方法中定義一個算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變算法結構的情況下,重新定義算法中的某些步驟。
從定義中,應該可以看出一部分,為了更好理解,下面就直接上例子:
二、模板方法例子
在敲代碼時,累了喝杯咖啡或者喝杯茶,會精神倍增。其實無論咖啡還是茶在沖的時間都是有講究的。這個在本文不是重點。下面分別描述一下沖泡咖啡和沖泡茶的過程:
兩種茶其分別的做法如下代碼:

1 public class Coffee 2 { 3 public void PrepareRecipe() 4 { 5 //燒水 6 BoilWater(); 7 //沖咖啡 8 BrewCoffeeGrinds(); 9 //倒入茶杯中 10 PourInCup(); 11 //加入糖和咖啡 12 AddSugarAndMilk(); 13 } 14 public void BoilWater() 15 { 16 Console.WriteLine("燒水"); 17 } 18 19 public void BrewCoffeeGrinds() 20 { 21 Console.WriteLine("沖咖啡"); 22 } 23 24 public void PourInCup() 25 { 26 Console.WriteLine("倒入杯子中"); 27 } 28 29 public void AddSugarAndMilk() 30 { 31 Console.WriteLine("加糖和牛奶"); 32 } 33 } 34 35 public class Tea 36 { 37 public void PrepareRecipe() 38 { 39 //燒水 40 BoilWater(); 41 //泡茶 42 SteepTeaBag(); 43 //倒入茶杯中 44 PourInCup(); 45 //加入檸檬 46 AddLemon(); 47 } 48 public void BoilWater() 49 { 50 Console.WriteLine("燒水"); 51 } 52 53 public void SteepTeaBag() 54 { 55 Console.WriteLine("泡茶"); 56 } 57 58 public void PourInCup() 59 { 60 Console.WriteLine("倒入杯子中"); 61 } 62 63 public void AddLemon() 64 { 65 Console.WriteLine("加檸檬"); 66 } 67 }
在面向對象語言中,是要講求復用的,現在燒水和帶入杯子的方法顯然是重復的,這樣就不符合對象村的村規——復用。
對比兩種做法,都是需要四個步驟,能不能把相同的使用一個基類,不同的部分分別由自己的去實現。其類圖如下
如果再細致觀察的話,我們的沖咖啡和泡茶以及加入咖啡和牛奶都是屬於差不多動作相同的。所以可以繼續抽象,抽象后的方法大致如此:
其實得出的也就是我們的模板方法。下面來看看模板方法模式的類圖:
三、模板方法類圖
從類圖可以看到我們把共用的方法放在抽象類中,用於復用。把不確定的方法,放入到具體類中,以便讓具體類可以很好的構造自己的方法。除此之外,還有個重點是無論是構造好的方法也好,還是抽象的方法也好,都會被裝入到一個TemplateMethod方法中。就像我們平時的做項目也是如此,大致的步驟需要:需求分析——>編碼——>測試,這些最基本的過程。但是具體的每一步,可能都是不同的,但是做項目的過程的幾個步驟是基本不變的,把步驟抽象成了模板,以后做項目的時間,都按這個步驟去做。這個就是我們的模板方法模式。到這里還是把我們的茶和咖啡沏好。
基類代碼:
public abstract class CoffeeinBeverage1 { public void BoilWater() { Console.WriteLine("燒水"); } public void PrepareRecipe() { BoilWater(); Brew(); PourInCup(); AddCondiments(); } public void PourInCup() { Console.WriteLine("倒入杯子中"); } public abstract void Brew(); public abstract void AddCondiments(); }
咖啡代碼:
public class Coffee1 : CoffeeinBeverage1 { public override void Brew() { Console.WriteLine("沖咖啡"); } public override void AddCondiments() { Console.WriteLine("加糖和牛奶"); }
茶代碼:
public class Tea1 : CoffeeinBeverage1 { public override void Brew() { Console.WriteLine("泡茶"); } public override void AddCondiments() { Console.WriteLine("加檸檬"); } }
四、模板方法注意的問題
在上面的茶水和咖啡中,現在是看起來能喝了,但是有個問題就是有些人喝咖啡喜歡不加任何調味料的。那么我們硬是給客人加,肯定是會生氣的。為了滿足這個要求,設計模式提供的有這個解決方案——使用鈎子。具體什么是鈎子,我們小的時間的課本上有個猴子撈月亮的圖片:
每個猴子不僅會被其他猴子拉住,還會伸出一個手去拉其他猴子。但最后一個手是沒有拉其他猴子了,但是還是要伸下去的,以防碰不到月亮。
同樣不知道方法能不能用得到,但是有對應的方法,具體的什么時間用得到,什么時間用不到,根據條件來判斷。
下面來看看鈎子如何在模板方法模式中使用的:
基類代碼:
public void PrepareRecipe() { BoilWater(); Brew(); PourInCup(); //有個判斷方法來添加調料 if (WantCondiments()) { AddCondiments(); } } /// <summary> /// 加入一個方法,用來判斷是否需要加調料 /// </summary> /// <returns></returns> public virtual bool WantCondiments() { return true; }
在子類中,可以通過不同的方式類覆蓋WantCondiment()方法。用來表示是否要加調料的標准,在此方法中注意必須加入virtual關鍵字,以便子類中使用override重寫。下面看看咖啡中的方法:
public override bool WantCondiments() { return false; }
下面是測試代碼:
class Program { static void Main(string[] args) { CoffeeinBeverage1 coffeninBeverage = new Coffee1(); coffeninBeverage.PrepareRecipe(); Console.ReadKey(); } }
輸出結果:
發現已經去掉了調料。
五、模板方法模式和策略模式以及工廠方法的對比
在看模板方法的時間,很容易想到工廠方法。因為他們都是讓具體的實現放在子類中,但是工廠方法主要是生產出產品,然后去應用產品。模板方法是在於依賴子類中的步驟中的其中幾個步驟,具體的步驟已經在基類中寫好了。
同樣模板方法模式和策略模式都是封裝算法。但是策略模式中的每個策略都是單獨的一個類。可以隨時去更改策略。模板方法模式雖然也是封裝了算法,其實主要在於封裝步驟,具體的實現是根據依靠各個子類。
除此之外,模板方法模式還涉及到一個好萊塢原則:
不要給我打電話,我會主動和你打電話。
在模板方法模式中扮演好萊塢的角色是抽象類,子類是演員的角色。
一般需要調用子類中的方法都已經在模板中定義好了,需要時,會主動聯系各個步驟,最好在子類中不要去調用抽象類中的方法具體方法,以防止子類和父類的調用的凌亂。
六、總結和源碼
本文主要先列出了模板方法模式的定義,然后通過例子來幫助理解模式,接着提出鈎子在模板方法模式中的使用,最后簡單對比了模板方法模式、策略模式以及工廠方法模式。