文章目錄
- 簡介
- 場景
- 解決
- 代碼
- 關鍵優化點
- 總結
簡介
模板方法是一種行為設計模式,它在超類中定義了一個算法的框架,允許子類在不修改結構的情況下重寫算法的特定步驟。
場景
假如你正在開發一款分析文檔的數據挖掘程序。用戶需要向程序輸入各種格式(PDF、DOC 或 CSV)的文檔,程序會試圖從這些文件中提取有意義的數據,再以統一的格式返回給用戶。
程序的首個版本只支持 DOC 文件。下個版本,程序需要支持 CSV 文件。一個月后,程序又需要從 PDF 文件中抽取數據。
一段時間后,你發現這三個類中包含許多相似代碼。盡管讀取不同格式數據的代碼完全不同,但數據處理和分析的代碼卻幾乎完全一樣。怎么在保持算法結構完整的情況下去除重復代碼?這是第一個問題。
還有另一個與使用這些類的客戶端代碼相關的問題:客戶端代碼中包含許多條件語句,因為它要根據不同的文檔類型選擇合適的處理過程。 如果所有處理數據的類都有相同的接口或基類,那你就可以去除客戶端代碼里的條件語句,轉而使用多態機制來調用處理對象的方法。
解決
模板方法模式建議把算法分解為一系列步驟,然后將這些步驟改寫為方法,最后在“模板方法”中依次調用這些方法。步驟可以是抽象的,也可以有一些默認的實現。為了能夠使用算法,客戶端需要自己提供子類,并且實現所有的抽象步驟。可能還需重寫一些步驟(但這一步中不包括模板方法自身)。
讓我們想想怎么在數據挖掘程序里運用這樣的方案。我們可為三個解析算法創建一個基類,這個類定義了一個模板方法-mine方法,這個方法會在內部調用不同的文檔處理步驟。
首先,我們將所有步驟聲明為抽象類型,強制要求子類自行實現這些方法。在我們的例子里,子類已經實現了必須的方法,因此我們只需調整這些方法的名稱,讓他跟超類的方法匹配就行了。
現在,讓我們看看怎么去除子類里的重復代碼。對于不同的文件格式,打開和關閉文件以及讀取解析數據的代碼都不同,所以不需要修改這些方法。但分析原始數據和生成報告等其他步驟的實現方式非常相似,所以可以把他們提取到基類里,讓子類共享這些代碼。
所以我們有兩種類型的步驟:
- 抽象步驟必須由各個子類來實現
- 可選步驟已經有一些默認實現,但你仍然可以在需要時進行重寫
還有另一種名為鉤子 hook 的步驟。鉤子是內容為空的可選的步驟。即使子類不重寫鉤子,模板方法也能工作。鉤子通常放置在算法重要步驟的前后,為子類提供額外的算法擴展點。
代碼
// 抽象類定義模板方法
abstract class DataMiner {// 模板方法(不可被覆蓋)public final void processDocument() { openDocument();extractRawData();parseData(); // 抽象步驟analyzeData(); // 通用實現generateReport(); // Hook方法}// 文檔打開基礎實現protected void openDocument() { System.out.println("打開文檔...");}// 數據抽取基礎實現protected void extractRawData() { System.out.println("抽取原始數據...");}// 解析算法必須子類實現protected abstract void parseData(); // 通用分析實現protected void analyzeData() { System.out.println("執行數據聚類分析...");}/* 鉤子方法(可選覆蓋) */protected void generateReport() { System.out.println("生成基礎統計報表");}
}// 具體子類實現
class PDFDataMiner extends DataMiner {@Overrideprotected void parseData() { // 實現特定解析邏輯System.out.println("解析PDF版式結構");System.out.println("提取PDF文本流");}
}class CSVDataMiner extends DataMiner {@Overrideprotected void parseData() { System.out.println("識別CSV分隔符");System.out.println("映射CSV字段");}@Overrideprotected void generateReport() { // 自定義Hook實現super.generateReport();System.out.println("追加CSV格式驗證結果");}
}// 客戶端調用示例
class Client {public static void main(String[] args) {System.out.println("處理PDF文檔:");DataMiner pdfProcessor = new PDFDataMiner();pdfProcessor.processDocument(); // 執行完整流程System.out.println("\n處理CSV文檔:");DataMiner csvProcessor = new CSVDataMiner();csvProcessor.processDocument();}
}
關鍵優化點
- 固定流程:不可重寫的processDocument()確保流程一致
- 職責分離:各子類僅需實現格式相關解析邏輯
- 擴展能力:通過鉤子方法實現可選擴展(如CSV格式驗證)
總結
- 抽象類(Abstract-Class)會聲明算法步驟的方法(step1,step2…),以及依次調用它們的模板方法(templateMethod)。算法步驟可以被聲明為抽象類型,也可以提供一些默認實現。
- 具體類(Con-crete-Class)可以重寫所有步驟,但不能重寫模板方法自身。