Java設計模式-模板方法模式
模式概述
模板方法模式簡介
核心思想:定義一個操作中的算法骨架(模板方法),將算法中某些步驟的具體實現延遲到子類中完成。子類可以在不改變算法整體結構的前提下,重定義這些步驟的行為,從而實現代碼復用與擴展的平衡。
模式類型:行為型設計模式(關注對象間的交互與職責分配)。
作用:
- 復用公共邏輯:將多個子類共有的算法步驟提取到父類,避免代碼重復。
- 提高擴展性:子類僅需實現差異化的步驟,符合“開閉原則”(對擴展開放,對修改關閉)。
- 規范流程:父類通過模板方法固定算法的整體結構,確保子類行為的一致性。
典型應用場景:
- 多個子類有公共的行為邏輯,但部分步驟實現不同(如數據庫訪問:連接、執行SQL、關閉連接的流程固定,但不同數據庫的驅動實現不同)。
- 框架中需要控制子類的執行流程(如Spring的
JdbcTemplate
封裝了JDBC操作的通用流程,具體SQL執行由子類或回調實現)。 - 需要約束子類的行為,確保關鍵步驟不被遺漏(如訂單處理流程:下單→支付→發貨→通知,其中支付方式可自定義)。
我認為:模板方法模式是“流程標準化”與“步驟定制化”的完美結合,父類搭骨架,子類填細節。
課程目標
- 理解模板方法模式的核心思想和經典應用場景
- 識別應用場景,使用模板方法模式解決功能要求
- 了解模板方法模式的優缺點
核心組件
角色-職責表
角色 | 職責 | 示例類名 |
---|---|---|
抽象模板角色 | 定義模板方法(算法骨架)和基本方法(具體方法、抽象方法、鉤子方法) | AbstractBeverageMaker |
具體模板角色 | 繼承抽象模板角色,實現所有抽象方法,并可選重寫鉤子方法 | CoffeeMaker 、TeaMaker |
類圖
下面是一個簡化的類圖表示,展示了模板方法模式中的主要角色及其交互方式:
傳統實現 VS 模板方法模式
案例需求
案例背景:實現飲料制作功能(如咖啡、茶),通用流程為:燒水→沖泡→倒入杯子→添加調料(可選)。不同飲料的沖泡方式(如咖啡粉 vs 茶葉)和調料添加(如加糖 vs 不加)不同。
傳統實現(痛點版)
代碼實現:
// 傳統實現:每個飲料獨立編寫完整流程
class CoffeeMaker {public void makeCoffee() {boilWater(); // 重復代碼brewCoffee(); // 咖啡特有邏輯pourInCup(); // 重復代碼addSugar(); // 咖啡特有邏輯}private void boilWater() {System.out.println("燒水:煮沸100℃");}private void brewCoffee() {System.out.println("沖泡:用熱水沖咖啡粉");}private void pourInCup() {System.out.println("倒入杯子");}private void addSugar() {System.out.println("添加:糖和牛奶");}
}class TeaMaker {public void makeTea() {boilWater(); // 重復代碼brewTea(); // 茶葉特有邏輯pourInCup(); // 重復代碼// 茶不需要調料,無需添加}private void boilWater() {System.out.println("燒水:煮沸100℃"); // 重復代碼}private void brewTea() {System.out.println("沖泡:用熱水泡茶葉");}private void pourInCup() {System.out.println("倒入杯子"); // 重復代碼}
}
痛點總結:
- 代碼冗余:
boilWater()
、pourInCup()
等方法在每個子類中重復實現。 - 擴展性差:新增飲料(如果汁)需復制大量重復代碼,違反開閉原則。
- 流程不可控:無法保證所有飲料遵循相同的基礎流程(如漏掉“倒入杯子”步驟)。
模板方法模式 實現(優雅版)
代碼實現:
// 抽象模板角色:定義流程骨架
abstract class AbstractBeverageMaker {// 模板方法(final修飾,防止子類修改流程)public final void makeBeverage() {boilWater();brew(); // 調用抽象方法(子類實現)pourInCup();if (needAddCondiments()) { // 調用鉤子方法(控制是否添加調料)addCondiments();}}// 具體方法(通用邏輯)private void boilWater() {System.out.println("燒水:煮沸100℃");}// 抽象方法(子類必須實現)protected abstract void brew();// 具體方法(通用邏輯)private void pourInCup() {System.out.println("倒入杯子");}// 鉤子方法(默認不添加調料,子類可選重寫)protected boolean needAddCondiments() {return false;}// 鉤子方法關聯的具體操作(子類可選重寫)protected void addCondiments() {// 默認空實現}
}// 具體模板角色:咖啡制作
class CoffeeMaker extends AbstractBeverageMaker {@Overrideprotected void brew() {System.out.println("沖泡:用熱水沖咖啡粉");}@Overrideprotected boolean needAddCondiments() {return true; // 咖啡需要添加調料}@Overrideprotected void addCondiments() {System.out.println("添加:糖和牛奶");}
}// 具體模板角色:茶葉制作
class TeaMaker extends AbstractBeverageMaker {@Overrideprotected void brew() {System.out.println("沖泡:用熱水泡茶葉");}// 不重寫needAddCondiments(),默認不添加調料
}
優勢:
- 消除冗余:公共方法(如
boilWater()
)在抽象類中實現,子類無需重復。 - 流程可控:模板方法通過
final
修飾,確保子類無法修改基礎流程。 - 靈活擴展:子類僅需實現抽象方法(如
brew()
),并通過鉤子方法(needAddCondiments()
)控制可選邏輯。
局限:
- 類數量增加:每個差異化的子類需單獨定義,可能增加系統復雜度。
- 抽象類設計成本:需合理規劃抽象方法與鉤子方法,過度設計可能導致冗余。
模式變體
- 具體模板方法:將模板方法聲明為
final
,禁止子類修改算法骨架(強制遵循固定流程)。 - 鉤子方法(Hook Method):提供默認實現的方法(通常返回布爾值或空操作),子類可選擇是否重寫以影響模板方法的行為(如上述案例中的
needAddCondiments()
)。 - 參數化模板:在模板方法中添加參數,允許子類通過參數調整行為(如數據庫操作模板支持傳入事務隔離級別)。
最佳實踐
建議 | 理由 |
---|---|
抽象類保持穩定 | 模板方法模式的核心是流程固定,頻繁修改抽象類會導致所有子類連鎖改動。 |
鉤子方法提供默認實現 | 減少子類必須重寫的負擔,僅當需要差異化時才覆蓋。 |
避免過度抽象 | 若子類間差異極小(如僅有1個步驟不同),可能更適合直接繼承而非模板模式。 |
模板方法用final 修飾 | 防止子類意外修改算法骨架,確保流程一致性。 |
一句話總結
模板方法模式通過“父類定義流程骨架,子類實現差異化步驟”,在保證流程規范的同時,實現了代碼復用與靈活擴展。
如果關注Java設計模式內容,可以查閱作者的其他Java設計模式系列文章。😊