我使用過一個簡單的後臺服務框架.這個框架上手很容易,我只須要繼承一個基類,同時實現,或重寫(override)基類聲明的幾個接口(這些接口聲明爲虛函數,或者純虛函數),而後調用基類定義好的run()函數,即可以將框架代碼運行起來.run函數作的事情,是依序調用上述的幾個接口:算法
class Service { public : int run(){ // .... step1(); // 收包 , 解包 step2(); // 業務邏輯處理 step3(); // 回包 step4(); //資源釋放 //.... } protected: virtual int step1(){ // 收包,解包邏輯實現 //... } virtual int step3(){ // 回包邏輯實現 //... } virtual int step4(){ //資源釋放 //... } virtual int step2() =0 ; //純虛函數,派生類實現 }
其中收包,解包,回包,釋放資源等動做,框架會提供一份實現,因爲咱們有時候會採用其餘的數據協議,因此基類也將收包回包等函數聲明爲虛函數,容許咱們針對新的協議進行函數的重寫(override).而對於業務邏輯處理函數,也就是step2,框架沒法爲咱們實現,咱們須要根據具體的業務需求來實現該函數,在派生類中來實現step2函數:設計模式
class MyService : public Service{ int step2(){ // 具體業務邏輯的實現 } }
派生類實現了step2函數後,經過調用run函數來運行程序:app
void main (){ //...準備工做 Service * myService = new MyService(); if( myService->run()){ //...後續處理 } // ... }
咱們的後臺服務框架例子中,run函數定義了一個服務的穩定執行步驟,但某個步驟有着特定的需求沒法立刻定義,須要延遲到派生類中去實現,這時候就須要採用模板方法模式.模板方法模式要解決的問題是:如何在肯定穩定操做結構的前提下,靈活地應對各個子步驟的變化或者晚期實現需求?框架
李建忠老師曾提過,重構得到設計模式(Refactoring to Patterns).設計模式的應用不宜先入爲主,一上來就使用設計模式是對設計模式的最大誤用,在敏捷軟件開發中,提倡使用的是經過重構來得到設計模式,這也是最好的使用設計模式的方法.ide
而關於重構的關鍵技法,包括了:函數
接下來咱們來看看如何將一個程序,重構成模板方法模式.現代軟件專業分工以後,也出現了"框架與應用程序的劃分",框架實現人員先定義好框架的執行流程,也就是算法骨架(穩定),並提供可重寫(overide)的接口(變化)給應用開發人員,以開發適應其應用程序的子步驟.模板方法經過晚綁定,實現了框架與應用程序之間的鬆耦合.設計
如今咱們須要實現一個程序庫,須要四個步驟來完成相應功能.其中step1,step3步驟穩定,而step2,step4則根據不一樣應用的具體須要,自行定義其具體功能,庫開發人員沒法預先實現好step2,step4.那麼庫開發人員能夠先寫好:指針
// 庫開發人員 class Library{ public: void step1(){ // 步驟1的具體實現 //... } void step3(){ //步驟3的具體實現 //... }
應用程序開發人員則根據具體的應用需求,來實現剩餘的兩個步驟:code
//應用程序開發人員 class Application{ public: void step2(){ //步驟2的具體實現 //... } bool step4(){ //步驟4的具體實現 //... } }
而後應用程序開發人員還須要寫一個main方法,將步驟以某種流程串起來:對象
//穩定 public static void main(String args[]) { Library lib = new Library(); Application app = new Application(); lib.step1(); if (app.step2()) { lib.step3(); } app.step4(); }
這種辦法其實是一種C語言結構化的實現方式,雖然用的是C++,但沒有體現出面向對象的特性來.main方法中,四個步驟的調用過程是相對穩定的,咱們能夠把這種穩定提高到庫的實現中去,而應用程序開發人員,只須要實現"變化"的代碼便可.這就引出了第二種作法.
第二種作法,是庫開發人員不只實現step1(),step3(),同時將step2(),step4()聲明爲純虛函數,等待應用程序開發人員本身去實現這兩個純虛函數.注意到,main方法中定義的執行流程是相對穩定的,徹底能夠把這些步驟移動到庫類中去.
//庫開發人員 class Library{ public : void run (){ step1(); if(step2()){ //支持變化-->虛函數的多態調用 step3(); } step4(); //支持變化-->虛函數的多態調用 } protected: void step1(){ //穩定 //... } void step3(){ //穩定 //... } virtual bool step2() =0; //純虛函數 virtual void step4() = 0; //純虛函數 virtual ~Library(){ //... } };
注意step2,step4爲純虛函數,這是由於庫開發人員沒法知道怎麼寫,留給程序庫開發人員來實現,也就是"把實現延遲",這在C++中體現爲虛函數或純虛函數,由應用程序開發人員繼承Library以實現兩個純虛函數.這一段代碼實際上體現了大部分設計模式應用的特色,也就是在穩定中包含着變化,run函數的算法流程是穩定的,可是算法的某個步驟是可變的,可變的延遲實現:
class Application: public Library{ protected: virtual bool step2(){ //...子類重寫實現 } virtual void step4(){ //...子類重寫實現 } };
而後,應用程序開發人員,只須要經過多態指針來完成框架的使用:
public static void main(String args[]) { Library * ptr = new Application(); ptr->run(); delete ptr; }
指針ptr是一個多態指針,它聲明爲Library類型,實際指向的對象爲Application類型對象.它會調用到基類的run方法,遇到step2,step4函數時,經過虛函數機制,調用到派生類實現的step2,step4函數.
回顧兩種實現方式,咱們能夠發現,第一種實現方式中:
採用了模板方法模式的實現方式中:
通常來講,框架/組件/庫的實現,老是要先於應用程序的開發的.在第一種方式中,應用程序開發者(晚開發)的執行流程調用了庫開發者定義好的函數(早開發),稱爲早綁定,而反過來在模板方法模式中,庫開發者在執行流程中先調用了step2,step4函數,而這兩個函數須要延遲到應用程序開發人員真正實現時,才經過虛函數機制進行調用,這種方式則稱爲早綁定.這即是重構使用設計模式的技法: 早綁定->晚綁定.
回過頭來看看模板方法模式的定義:定義一個操做中的算法的骨架,而將一些步驟延遲到子類中.Template Method使得子類刻意不改變一個算法的結構便可重定義該算法的某些特定步驟.(< <設計模式> > GoF) 所謂的骨架,要求是相對穩定的,在上面的例子中,若是step1,step3也是不穩定的,那麼該情景下就不適用於適用設計模式,緣由是軟件體系中全部的東西都不穩定.設計模式的假設條件是必須有一個穩定點,若是沒有穩定點,那麼設計模式沒有任何做用.反過來講,若是全部的步驟都是穩定的,這種極端狀況也不適用於適用設計模式.設計模式老是處理"穩定中的變化"這種情景.設計模式最大的做用,是在穩定與變化之間尋找隔離點,而後來分離它們,從而來管理變化.從而咱們也可以獲得啓發,學會分析出軟件體系結構中哪部分是穩定的,哪部分是變化的,是學好設計模式的關鍵點.
再來看一看模板方法設計模式的結構:其中TemplateMethod() 方法也就是咱們上面所說的run函數,它相對穩定,primitiveOperation1(),primitiveOperation2()爲兩個變化的函數,可由派生類實現,在TemplateMethod()中調用步驟.在下圖中,紅色圈爲穩定的部分,而黑色圈爲變化的部分.
在面向對象的時代,絕大多數的框架設計都使用了模板方法模式.做爲一個應用程序開發人員,咱們每每只須要實現幾個步驟,框架便會把咱們的步驟"串接"到執行流程中,有時候甚至連main函數都不用咱們去實現.這樣子也有弊端,咱們看不見框架的執行流程,執行細節是怎麼樣的,每每有一種"只見樹木不見森林"的感受.
最後來總結如下模板方法設計模式.Template Method設計模式是一種很是基礎性的設計模式,它要解決的問題是如何在肯定穩定操做結構的前提下,來靈活應對各個子步驟的變化或者晚期實現需求.它使用了函數的多態性機制,爲應用程序框架提供了靈活的拓展點,是代碼複用方面的基本實現結構.Template Method設計模式明顯劃分了穩定與變化的關係,除了靈活應對子步驟的變化外,也是晚綁定的典型應用,經過反向控制結構,使得早期的代碼能夠調用晚期代碼.而在具體實現上,被Template Method調用的虛函數,能夠具備實現,也能夠沒有任何實現,這在C++中體現爲虛函數或者純虛函數,通常將這些函數設置爲proteced方法.