讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議108:反射讓模板方法模式更強大 >

建議108:反射讓模板方法模式更強大

模板方法模式(Template Method Pattern)的定義是:定義一個操作中的算法骨架,將一些步驟延遲到子類中,使子類不改變一個算法的結構即可重定義該算法的某些特定步驟。簡單地說,就是父類定義抽像模板作為骨架,其中包括基本方法(是由子類實現的方法,並且在模板方法被調用)和模板方法(實現對基本方法的調度,完成固定的邏輯),它使用了簡單的繼承和覆寫機制,我們來看一個基本的例子。

我們經常會開發一些測試或演示程序,期望系統在啟動時自行初始化,以方便測試或講解,一般的做法是寫一個SQL文件,在系統啟動前手動導入,不過,這樣不僅麻煩而且還容易出現錯誤,於是我們就自己動手寫了一個初始化數據的框架:在系統(或容器)啟動時自行初始化數據。但問題是每個應用程序要初始化的內容我們並不知道,只能由實現者自行編寫,那我們就必須給作者預留接口,此時就得考慮使用模板方法模式了,代碼如下:


public abstract class AbsPopulator{

//模板方法

public final void dataInitialing()throws Exception{

//調用基本方法

doInit();

}

//基本方法

protected abstract void doInit();

}


這裡定義了一個抽像模板類AbsPopulator,它負責數據初始化,但是具體要初始化哪些數據則是由doInit方法決定的,這是一個抽像方法,子類必須實現,我們來看一個用戶表數據的加載:


public class UserPopulator extends AbsPopulator{

protected void doInit(){

/*初始化用戶表,如創建、加載數據等*/

}

}


該系統在啟動時,查找所有的AbsPopulator實現類,然後dataInitialing實現數據的初始化。那讀者可能要想了,怎麼讓容器知道這個AbsPopulator類呢?很簡單,如果是使用Spring作為IoC容器的項目,直接在dataInitialing方法上加上@PostConstruct註解,Spring容器啟動完畢後會自動運行dataInitialing方法,由於這裡的原理超出了本書的範疇,不再贅述。

現在的問題是:初始化一張User表需要非常多的操作,比如先建表,然後篩選數據,之後插入,最後校驗,如果把這些都放到一個doInit方法裡會非常龐大(即使提煉出多個方法承擔不同的職責,代碼的可讀性依然很差),那該如何做呢?又或者doInit是沒有任何的業務意義的,是否可以起一個優雅而又動聽的名字呢?

答案是我們可以使用反射增強模板方法模式,使模板方法實現對一批固定規則的基本方法的調用。代碼是最好的交流語言,我們看看怎麼改造AbsPopulator類,代碼如下:


public abstract class AbsPopulator{

//模板方法

public final void dataInitialing()throws Exception{

//獲得所有的public方法

Methodmethods=getClass().getMethods();

for(Method m:methods){

//判斷是否是數據初始化方法

if(isInitDataMethod(m)){

m.invoke(this);

}

}

}

//判斷是否是數據初始化方法,基本方法鑒別器

private boolean isInitDataMethod(Method m){

return m.getName().startsWith(\"init\")//init開始

&&Modifer.isPublic(m.getModifers())//公開方法

&&m.getReturnType().equals(Void.TYPE)//返回值是void

&&!m.isVarArgs()//輸入參數為空

&&!Modifer.isAbstract(m.getModifers());//不能是抽像方法

}

}


在一般的模板方法模式中,抽像模板(這裡是AbsPopulator類)需要定義一系列的基本方法,一般都是protected訪問級別的,並且是抽像方法,這標誌著子類必須實現這些基本方法,這對子類來說既是一個約束也是一個負擔。但是使用了反射後,不需要定義任何抽像方法,只需定義一個基本方法鑒別器(例子中isInitDataMethod)即可加載符合規則的基本方法。鑒別器在此處的作用是鑒別子類方法中哪些是基本方法,模板方法(例子中的dataInitialing)則根據基本方法鑒別器返回的結果通過反射執行相應的方法。

此時,如果需要進行大量的數據初始化工作,子類的實現就非常簡單了,代碼如下:


public class UserPopulator extends AbsPopulator{

public void initUser(){

/*初始化用戶表,如創建、加載數據等*/

}

public void initPassword(){

/*初始化密碼*/

}

public void initJobs(){

/*初始化工作任務*/

}

}


UserPopulator類中的方法只要符合基本方法鑒別器條件即會被模板方法調用,方法的數據量也不再受父類的約束,實現了子類靈活定義基本方法、父類批量調用的功能,並且縮減了子類的代碼量。

如果讀者熟悉JUnit的話,就會看出此處的實現與JUnit非常類似,JUnit4之前要求測試的方法名必須是以test開頭的,並且無返回值、無參數,而且有public修飾,其實現的原理與此非常相似,讀者有興趣可以看看JUnit的源代碼。

注意 決定使用模板方法模式時,請嘗試使用反射方式實現,它會讓你的程序更靈活、更強大。