1. 摘要
在本文中,我們來對模板方法模式做一個全面的瞭解。
2. 求職簡訊
呵呵,如果各位不介意,希望在這裡做一個求職的廣告。
語言方向:Java/C#/VB,語言熟悉程度降序排列。
求職方向:開發,測試等與IT相關工作。
月薪要求:1000元以上
工作地點:北京
3. 開門見山
模板方法,定義一個操作中演算法骨架,而將一些步驟延遲到子類中去實現。
讓我們先來看看模板方法的:(引自TerryLee《.NET設計模式——模板方法模式》 )
這個模式的本身很容易理解,我就不自己去畫了。
我們看到這個模式的關鍵在於父類中的TemplateMethod()方法,正是這個方法提供了我們演算法的骨架。
曾經和朋友討論過設計模式,我們一致認為,模板方法是GOF23種設計模式中最重要的設計模式,但是我們卻認為,這個模式,我們沒有必要把他當作設計模式,甚至可以把他當作一種很普通的繼承規則。
4. 模板方法的目的
模板方法的目的,我認為有兩點:
A. 減少重複代碼。
B. 防止調用出錯。
這樣的話,我們來歸結一下使用模板方法的步驟:
A. 識別
B. 分離
C. 實現
也就是說我們首先要從一系列的演算法中去找到,他們的共同部分在哪,稱之為識別。然後我們將不同的代碼抽取出來,稱之為分離。最後,我們用繼承將抽取出來的方法在子類中予以實現。
5. 數組操作之模板方法
在一個複雜演算法中,總是會在一個演算法中牽扯到很多演算法的嵌套使用。
例如我們要在一個對象昂數組中,將第N大的對象進行某個操作。那麼這樣,我們可能要把整個演算法分為三步:
A. 將對象數組按照一定規則排序
B. 找出對象數組第N大的數,也許我們不知道這個N,那麼這個N可能是從資料庫裡讀,可能是從檔案裡找,也可能是一個隨機數
C. 對這個對象進行某項操作,可能是刪除,可能是對這個對象的某個屬性做修改。
那麼我們需要這樣寫:
abstract class ArrayOperate{ public void TemplateMethod() { List<People> peopleArray = new List<People> { new People("111", 1), new People("222", 2), new People("333", 3), new People("444", 4), new People("555", 5) }; Sort(ref peopleArray); People p = FindOnePeople(peopleArray); p = ChangeSomeoneName(p); Display(p); } public abstract void Sort(ref List<People> peopleArray); public abstract People FindOnePeople(List<People> peopleArray); public abstract People ChangeSomeoneName(People p); public abstract void Display(People p);}
class ConOperate:ArrayOperate{ public override void Sort(ref List<People> peopleArray) { peopleArray.Sort(SortPeople); } public override People FindOnePeople(List<People> peopleArray) { Random r = new Random(); int randomInt = r.Next(peopleArray.Count); return peopleArray[randomInt]; } public override People ChangeSomeoneName(People p) { p.Name = "123"; return p; } public override void Display(People p) { Console.WriteLine("名字是:"+p.Name + "\n" +"年齡是:"+ p.Age); } private static int SortPeople(People p1, People p2) { return String.Compare(p1.Name, p2.Name); }}
class People{ private string name; private int age; public string Name { get { return name; } set { name = value; } } public int Age { get { return age; } } public People(string name, int age) { this.name = name; this.age = age; }}
class Program{ static void Main(string[] args) { ArrayOperate ao = new ConOperate(); ao.TemplateMethod(); }}
在這裡,我們就實現了方法的可替換。我們在父類中指定了方法的一個執行過程,然後要求子類去實現具體的方法。
6. 模板方法之深究
讓我們返回來看:
模板方法的效用一:節省子類代碼。
這樣,如果我們在父類把方法全部abstract,那麼我們就沒有做到減少子類代碼的作用。
因此,寫模板方法很容易,但是寫一個好的模板方法就沒那麼容易了。
我們要做到良好的抽取,把固定的實現方法放在父類方法中去實現。
模板方法效用二:防止叫用作業
在很多關於設計模式的書上,都是把父類的模板方法換成了一個簡單的方法的堆積,如Display**1,Display*2等等。這樣就給很多讀者造成了一種錯覺,覺得模板方法就是去調用下面的方法,其實並不盡然。
7. 再論模板——泛型
我之前一直說,在很多進階語言,諸如C#,Java中提供了很多實現,這使得很多模式成為了一種思路,而並非必須的實現。
在這個小節,我們來探討下模板方法的一個思維變種——泛型
在C#2.0中,一個很重要的特性——泛型。我們可能在很多參考書上都會看到這樣的說法,C#的泛型,類似於C++的模板。
其實,C#的泛型也是模板方法的一個思維變種,他是對於不同類型都提供了同一套模板去實現。
8. 類爆炸
我們考慮一下這個情況,在模板方法中,如果同一套模板需要多種擴充的話,那是不是又要寫很多繼承的子類呢?
這樣,我又要提到了一個我之前說過無數次的概念:爆炸——這次是類爆炸。
假設我們要對一個數組進行這樣或者那樣的操作,比如我們要先排序,然後在怎麼怎麼樣。
那我們比如可能要根據數組的不同結構採用不同的排序方式,也許有的用冒泡,有的用快排,有的用歸併等等。那麼我們就得對每一個排序都寫一套對應的子類,好麻煩。
怎麼辦?怎麼辦?怎麼辦?
9. 委託事件改善模板方法
class Program{ static void Main(string[] args) { ArrayOperate ao = new ConOperate(); ao.Sort += new SortDelegate( delegate(ref List<People> peopleList) { peopleList.Sort(SortPeople); } ); ao.TemplateMethod(); } private static int SortPeople(People p1, People p2) { return String.Compare(p1.Name, p2.Name); }}public delegate void SortDelegate(ref List<People> list);abstract class ArrayOperate{ public event SortDelegate Sort; public void TemplateMethod() { List<People> peopleArray = new List<People> { new People("111", 1), new People("222", 2), new People("333", 3), new People("444", 4), new People("555", 5) }; Sort(ref peopleArray); People p = FindOnePeople(peopleArray); p = ChangeSomeoneName(p); Display(p); } //public abstract void Sort(ref List<People> peopleArray); public abstract People FindOnePeople(List<People> peopleArray); public abstract People ChangeSomeoneName(People p); public abstract void Display(People p);}class ConOperate:ArrayOperate{ //public override void Sort(ref List<People> peopleArray) //{ // peopleArray.Sort(SortPeople); //} public override People FindOnePeople(List<People> peopleArray) { Random r = new Random(); int randomInt = r.Next(peopleArray.Count); return peopleArray[randomInt]; } public override People ChangeSomeoneName(People p) { p.Name = "123"; return p; } public override void Display(People p) { Console.WriteLine("名字是:"+p.Name + "\n" +"年齡是:"+ p.Age); } private static int SortPeople(People p1, People p2) { return String.Compare(p1.Name, p2.Name); }}public class People{ private string name; private int age; public string Name { get { return name; } set { name = value; } } public int Age { get { return age; } } public People(string name, int age) { this.name = name; this.age = age; }}
這樣如果再次遭遇我們需要修改排序方式的話,是不是省了很多事呢?
可能這時候有人問,你都能想到的東西,肯定有很多人也想到過!但是這並沒有被推廣,原因是什麼呢?
那就讓我們來分析這種方式的特點:
1. 委託其實就是一類方法的模板。
2. 原有的模板方法是將實現對客戶隱藏,這樣的話,相當於把實現的選擇轉交給客戶去實現。客戶去選擇模板的具體實現。
10. 委託到底
既然我們都用了委託“改善”模板方法,既然我們已經賦予了客戶的實現權利,那麼我們試著把權利再給大一些。
很多時候,有這樣一種情況,客戶可以根據自己去選擇方法的實現順序,那麼這個時候,我們就可以用委託去實現。
方法是:我們在一個對用戶端公開的類中去實現一些方法,然後我們把這些方法暴露給用戶端,讓用戶端根據自己的選擇去組裝這些方法。這其實也是個不錯的方法。
比如我們有一個類,裡面實現了很多的密碼編譯演算法,然後我們要用一個加密流去對密碼進行一個加密過程(註:這裡我們不討論裝飾模式)。那麼我們就可以讓客戶根據自己的愛好和解密習慣去選擇他們自己的加密順序。
11. 模板方法宏觀化
我們在這裡討論都是在類和介面之間去搞來搞去,其實我們一樣可以把模板方法宏觀化。
其實我們想一想,設計師,架構師在做一些什麼工作呢?模板方法!
他們為我們提供一個軟體系統架構的模板,然後讓我們去實現具體的細節,就是這樣。
因此,我們可以提供一個演算法的模板,同樣可以提供一個系統過程的模板,提供一個軟體的模板。
12. 模式比拼
有些朋友跟我說,不就是規定一個順序嗎?這樣看來和建造者模式很像啊。
其實不然,他們解決的完全是兩種問題:
模板方法是提供一個演算法的骨架。
而建造者模式最後完成的是一個對象的組裝。
13. 模式總結
模板方法是一個簡單但是很重要的模板方法,他最大地解釋了繼承的威力。
甚至有人說:如果你只懂一個設計模式,那麼這個模式就是模板方法模式。
文章至此為止,感謝各位的關注,同時也希望各位多多指教,提出意見,我會改正,讓自己越做越好。