說到模板方法模式,它多是一個讓咱們深刻骨髓而又不自知的模式了,由於它在咱們開發過程當中會常常遇到,而且也很是簡單。只不過,不少時候咱們並不知道它就是模板方法模式而已。不負責任的說,當咱們用到override
關鍵字重寫父類方法的時候,十有八九就跟模板方法模式有關了。git
先看一下模板方法模式的定義,模板方法模式定義了一個操做中的算法的框架,而將一些步驟延遲到子類中。使得子類能夠不改變一個算法的結構便可重定義該算法的某些步驟。github
這裏延遲到子類說的玄乎,其實就是子類繼承並實現父類中的抽象方法(abstract
),而重定義該算法的某些步驟指的就是子類重寫父類的虛方法(virtual
)。不過,不論是哪個,子類都須要用到override
。算法
咱們仍是經過一個例子來解釋模板方法模式,先來一個經典的腦筋急轉彎。框架
答案是三步:
- 第一步,把冰箱門打開
- 第二步,把大象放進去
- 第三步,把冰箱門關上ide
對應到前面的定義,這裏把大象裝進冰箱的步驟就是算法的框架,而其中的每一步就是算法的具體步驟。咱們用代碼實現看看:函數
public abstract class AnimalToFridge { public void Do() { OpenFridge(); PutIntoFridge(); CloseFridge(); } private void OpenFridge() { Console.WriteLine("把冰箱門打開"); } public abstract void PutIntoFridge(); private void CloseFridge() { Console.WriteLine("把冰箱門關上"); } }
上面定義了一個把動物放進冰箱的基類,Do()
方法定義了把大象裝進冰箱的算法骨架,其中,打開冰箱和關閉冰箱兩個步驟是固定不變的,變化只是把什麼動物放進去。學習
再定義一個把大象放冰箱的子類,繼承自上面的基類:設計
public class ElephantToFridge:AnimalToFridge { public override void PutIntoFridge() { Console.WriteLine("把大象放進去"); } }
使用時,咱們只須要調用Do()
方法就能夠完成把大象放冰箱的動做了:code
static void Main(string[] args) { AnimalToFridge elephantToFridge = new ElephantToFridge(); elephantToFridge.Do(); }
這時候若是咱們要把其餘動物放進去,只須要繼承AnimalToFridge
就能夠了,例如,咱們把狗放進冰箱:對象
public class DogToFridge: AnimalToFridge { public override void PutIntoFridge() { Console.WriteLine("把狗放進去"); } }
可是你覺得這麼簡單就結束了嗎?知道這個腦筋急轉的朋友應該都知道它還有第二問。
答案是四步:
- 第一步,把冰箱門打開
- 第二步,把大象弄出來
- 第三步,把長頸鹿放進去
- 第四步,把冰箱門關上
咱們能夠分析一下需求,也就是說,把大象放進以前不須要先把什麼拿出來,可是放長頸鹿須要先把大象弄出來。再進一步分析的話,能夠推測把雞蛋、螞蟻這樣的小東西放進去,即便裏面有大象,應該也不須要先把大象拿出來,而放獅子、老虎這樣的大型動物就須要清空冰箱。爲了知足這樣的需求,咱們的虛方法就登場了,代碼能夠作以下改進:
public abstract class AnimalToFridge { public void Do() { OpenFridge(); BeforePutIntoFridge(); PutIntoFridge(); CloseFridge(); } private void OpenFridge() { Console.WriteLine("把冰箱門打開"); } protected virtual void BeforePutIntoFridge() { } protected abstract void PutIntoFridge(); private void CloseFridge() { Console.WriteLine("把冰箱門關上"); } }
基類中增長了一個BeforePutIntoFridge()
的虛方法,方法只有一個空的實現(固然,若是須要的話,也能夠添加具體內容),除此以外,我把虛方法和抽象方法的訪問修飾符都改爲protected
了,由於,算法的單個步驟不該該被客戶端直接調用,調用了也沒有任何意義。這樣,咱們的大象和長頸鹿子類就能夠以下實現了:
public class ElephantToFridge : AnimalToFridge { protected override void PutIntoFridge() { Console.WriteLine("把大象放進去"); } } public class GiraffeToFridge : AnimalToFridge { protected override void BeforePutIntoFridge() { Console.WriteLine("把大象弄出來"); } protected override void PutIntoFridge() { Console.WriteLine("把長頸鹿放進去"); } }
ElephantToFridge
類不重寫父類的BeforePutIntoFridge()
方法,而GiraffeToFridge
類重寫了,也就是定義中所說的重定義了該算法的某些步驟了。
好了,這樣就改造完成並知足需求了,咱們再來看一下最終的總體類圖:
這就是模板方法模式,其實就是對繼承加抽象方法和虛方法的使用,這可能算是繼承的巔峯時刻了吧,在其餘模式中只有被吐槽的命。
再抽象一下就能夠獲得模板方法模式的UML類圖了:
在學習模板方法模式的時候,咱們可能會常常聽到鉤子函數這個概念。鉤子就是給子類一個受權,讓子類來可重定義模板方法的某些步驟,聽着高大上,說白了就是虛方法而已。
建造者模式不少地方跟模板方法模式是很類似的,例如,他們都是經過繼承實現,都會把易變化的部分延遲到子類實現,而且都有一個方法封裝骨架,只不過,建造者模式延遲到子類的是各部件的建立,封裝的是最後的構建流程。而模板方法模式延遲到子類實現的是算法的某些步驟,封裝的是算法骨架。也就是說若是你認可建立對象也是一種算法的話,那兩者其實就差很少了。不過呢?他們也是有區別的,由於建造者模式中,各部件的建造須要客戶端配合完成,所以,建造各部件的方法須要是public
的,而模板方法模式中,各單獨的算法步驟不該該被客戶端直接調用,所以一般是protected
的。不過,儘管如此,他們的設計思想確實是大同小異的。
說到這裏,還記得建造者模式是如何經過使用委託來緩解子類過多的問題的嗎?既然模板方法模式與建造者模式類似,那麼處理方式也應該類似了,咱們看看最終實現效果:
public class AnimalToFridge { public void Do(Action beforePutIntoFridge,Action putIntoFridge) { OpenFridge(); beforePutIntoFridge?.Invoke(); putIntoFridge?.Invoke(); CloseFridge(); } private void OpenFridge() { Console.WriteLine("把冰箱門打開"); } private void CloseFridge() { Console.WriteLine("把冰箱門關上"); } }
直接把抽象方法和虛方法都去掉了,換成了委託,只保留了算法骨架。這樣作好處很明顯,不須要子類了,不管多少動物,所有都經過委託搞定了。不過缺點也很明顯,算法的實現交給了客戶端,給客戶端的使用帶來了不小的負擔,而且若是調用位置不少,還會致使大量代碼重複,難以維護。
所以,模板方法模式具體該如何使用還得視狀況而定。