設計模式(二十三)行為型:模板方法模式詳解
模板方法模式(Template Method Pattern)是 GoF 23 種設計模式中的行為型模式之一,其核心價值在于定義一個操作中的算法骨架,而將一些步驟延遲到子類中實現,使得子類可以在不改變算法結構的前提下重新定義算法的某些特定步驟。它通過“父類控制流程,子類實現細節”的方式,實現了代碼復用與行為擴展的完美平衡。模板方法模式是構建框架、標準化流程、實現鉤子機制、統一處理邏輯(如數據處理、構建流程、業務審批流)的基石,是“好萊塢原則”(Don’t call us, we’ll call you)在面向對象設計中的經典體現。
一、詳細介紹
模板方法模式解決的是“多個類實現同一算法,算法結構相同但某些步驟的具體實現不同,且需要防止子類改變整體流程”的問題。在傳統設計中,每個類可能都實現完整的算法,導致大量重復代碼;或者使用條件分支,導致邏輯混亂。模板方法模式通過抽象類定義算法的固定骨架(模板方法),將可變步驟聲明為抽象方法或鉤子方法,由子類實現。
該模式包含以下核心角色:
- AbstractClass(抽象類):定義算法的骨架(模板方法),該方法通常是一個
final
方法,防止子類覆蓋。它包含:- 模板方法(Template Method):定義算法的步驟順序,調用原語操作(Primitive Operations)。
- 抽象原語操作(Abstract Primitive Operations):聲明為
abstract
,必須由子類實現,代表算法中可變的步驟。 - 具體原語操作(Concrete Primitive Operations):在抽象類中提供默認實現,子類可選擇性覆蓋。
- 鉤子方法(Hook Methods):在抽象類中提供空實現或默認實現的
protected
方法,子類可選擇性覆蓋以“掛鉤”到算法流程中,實現條件邏輯或擴展點。
- ConcreteClass(具體子類):繼承
AbstractClass
,實現所有抽象原語操作,并可選擇性覆蓋具體原語操作或鉤子方法,以定制算法的特定行為。
模板方法模式的關鍵優勢:
- 代碼復用:算法骨架在父類中定義,避免重復。
- 控制流程:父類控制算法的整體結構,防止子類破壞流程。
- 擴展性:子類通過實現抽象方法或覆蓋鉤子來擴展行為。
- 符合開閉原則:新增行為通過添加新子類實現,無需修改父類。
- 標準化:強制所有子類遵循相同的算法流程。
與“策略模式”相比,策略模式在運行時通過組合選擇算法,模板方法在編譯時通過繼承固定流程;策略更靈活,模板方法更強調流程控制。與“狀態模式”相比,狀態模式關注狀態驅動的行為切換,模板方法關注流程中步驟的定制。與“命令模式”相比,命令封裝請求,模板方法封裝算法結構。
模板方法模式適用于:
- 框架設計(如 Spring MVC 的
AbstractController
)。 - 標準化業務流程(如訂單處理、審批流)。
- 構建工具(如編譯、打包、部署流程)。
- 數據處理管道(如 ETL 流程)。
- 圖形渲染流程。
- 單元測試框架的
setUp
/tearDown
。
二、模板方法模式的UML表示
以下是模板方法模式的標準 UML 類圖:
圖解說明:
AbstractClass
定義templateMethod()
(通常為final
),它按固定順序調用primitiveOperation1()
,primitiveOperation2()
,concreteOperation()
,hookMethod()
。primitiveOperation1()
和primitiveOperation2()
是抽象方法,必須由子類實現。concreteOperation()
在父類中有具體實現,子類可覆蓋。hookMethod()
是鉤子,有默認實現(可能為空),子類可選擇性覆蓋以插入自定義邏輯。ConcreteClassA
和ConcreteClassB
實現抽象方法,并可選擇覆蓋其他方法。
三、一個簡單的Java程序實例及其UML圖
以下是一個跨平臺軟件構建流程的示例,包含編譯、測試、打包、部署步驟,不同平臺(Windows, Linux)實現不同。
Java 程序實例
// 抽象類:軟件構建流程
abstract class SoftwareBuildProcess {// 模板方法:定義構建流程的骨架 (final 防止子類修改流程)public final void build() {System.out.println("🚀 開始構建流程...");checkoutCode();compile();runTests();// 鉤子方法:子類可決定是否打包if (shouldPackage()) {packageApplication();}// 鉤子方法:子類可決定是否部署if (shouldDeploy()) {deploy();}cleanup();System.out.println("? 構建流程完成。\n");}// 具體原語操作:在抽象類中實現,所有子類共享private void checkoutCode() {System.out.println(" 🔁 1. 檢出代碼 (從版本控制系統)");// 模擬操作}// 具體原語操作:提供默認實現,子類可覆蓋protected void cleanup() {System.out.println(" 🧹 6. 清理臨時文件 (默認實現)");// 默認清理邏輯}// 鉤子方法:提供默認行為(true/false),子類可覆蓋以控制流程protected boolean shouldPackage() {return true; // 默認打包}protected boolean shouldDeploy() {return false; // 默認不部署}// 抽象原語操作:必須由子類實現protected abstract void compile();protected abstract void runTests();protected abstract void packageApplication();protected abstract void deploy();
}// 具體子類:Windows 構建流程
class WindowsBuildProcess extends SoftwareBuildProcess {@Overrideprotected void compile() {System.out.println(" ?? 2. 在 Windows 上編譯 (使用 MSVC)");// 調用 Windows 編譯器}@Overrideprotected void runTests() {System.out.println(" 🧪 3. 運行 Windows 測試套件");// 執行 Windows 測試}@Overrideprotected void packageApplication() {System.out.println(" 📦 4. 打包為 Windows 安裝程序 (.exe)");// 生成 .exe 安裝包}@Overrideprotected void deploy() {System.out.println(" 🚀 5. 部署到 Windows 服務器");// 部署邏輯}// 覆蓋鉤子:Windows 構建默認不部署@Overrideprotected boolean shouldDeploy() {return false;}// 覆蓋具體方法:Windows 特定的清理@Overrideprotected void cleanup() {System.out.println(" 🧹 6. 清理 Windows 臨時文件和 .obj 文件");// Windows 清理邏輯}
}// 具體子類:Linux 構建流程
class LinuxBuildProcess extends SoftwareBuildProcess {@Overrideprotected void compile() {System.out.println(" ?? 2. 在 Linux 上編譯 (使用 GCC)");// 調用 GCC 編譯器}@Overrideprotected void runTests() {System.out.println(" 🧪 3. 運行 Linux 測試套件");// 執行 Linux 測試}@Overrideprotected void packageApplication() {System.out.println(" 📦 4. 打包為 Linux 包 (.deb 或 .rpm)");// 生成 .deb 包}@Overrideprotected void deploy() {System.out.println(" 🚀 5. 部署到 Linux 服務器 (使用 SSH)");// 部署邏輯}// 覆蓋鉤子:Linux 構建在 CI 環境中自動部署@Overrideprotected boolean shouldDeploy() {// 可根據環境變量決定return System.getenv("CI_ENV") != null;}// 覆蓋鉤子:Linux 構建時,如果測試失敗則不打包@Overrideprotected boolean shouldPackage() {// 簡化:假設測試總是通過return true;}
}// 具體子類:快速構建(跳過測試和打包,僅用于開發)
class QuickBuildProcess extends SoftwareBuildProcess {@Overrideprotected void compile() {System.out.println(" ? 2. 快速編譯 (僅編譯修改的文件)");// 快速編譯邏輯}@Overrideprotected void runTests() {System.out.println(" ?? 3. 跳過測試 (開發模式)");// 不運行測試}@Overrideprotected void packageApplication() {System.out.println(" ?? 4. 跳過打包 (開發模式)");// 不打包}@Overrideprotected void deploy() {System.out.println(" ?? 5. 跳過部署 (開發模式)");// 不部署}// 覆蓋鉤子:快速構建不打包@Overrideprotected boolean shouldPackage() {return false;}// 覆蓋鉤子:快速構建不部署@Overrideprotected boolean shouldDeploy() {return false;}
}// 客戶端使用示例
public class TemplateMethodPatternDemo {public static void main(String[] args) {System.out.println("🏗? 跨平臺軟件構建系統 - 模板方法模式示例\n");// 構建 Windows 版本System.out.println("--- 構建 Windows 版本 ---");SoftwareBuildProcess windowsBuild = new WindowsBuildProcess();windowsBuild.build();// 構建 Linux 版本System.out.println("--- 構建 Linux 版本 ---");SoftwareBuildProcess linuxBuild = new LinuxBuildProcess();// 模擬 CI 環境System.setProperty("CI_ENV", "true");linuxBuild.build();// 開發者快速構建System.out.println("--- 開發者快速構建 ---");SoftwareBuildProcess quickBuild = new QuickBuildProcess();quickBuild.build();}
}
實例對應的UML圖(簡化版)
運行說明:
SoftwareBuildProcess
定義build()
模板方法,固定流程:檢出 -> 編譯 -> 測試 -> (條件打包) -> (條件部署) -> 清理。compile()
,runTests()
,packageApplication()
,deploy()
是抽象方法,由子類實現。cleanup()
是具體方法,有默認實現,子類可覆蓋。shouldPackage()
和shouldDeploy()
是鉤子方法,子類可覆蓋以控制流程分支。WindowsBuildProcess
,LinuxBuildProcess
,QuickBuildProcess
實現各自平臺的細節,并通過覆蓋鉤子定制流程。
四、總結
特性 | 說明 |
---|---|
核心目的 | 定義算法骨架,延遲步驟實現到子類 |
實現機制 | 抽象類定義模板方法(final),子類實現抽象方法 |
優點 | 代碼復用、控制流程、符合開閉原則、標準化流程、支持鉤子擴展 |
缺點 | 依賴繼承(靈活性低于組合)、類爆炸(過多子類)、父類改動影響所有子類 |
適用場景 | 框架設計、標準化流程、構建腳本、數據處理管道、業務審批流 |
不適用場景 | 流程不固定、需要運行時動態組合行為、避免繼承的場景 |
模板方法模式使用建議:
- 模板方法通常聲明為
final
,防止子類破壞流程。 - 鉤子方法是強大的擴展點,用于條件邏輯或可選步驟。
- 可結合“工廠方法模式”創建具體子類。
- 在 Java 中,
Comparator
的compare()
可視為一種函數式模板方法。
架構師洞見:
模板方法模式是“框架設計”的靈魂。在現代架構中,其思想已演變為框架與庫的根本區別:庫是“你調用我”,框架是“我調用你”(好萊塢原則)。Spring 框架的JdbcTemplate
,RestTemplate
是其典型應用;JUnit 的@Before
,@After
是鉤子方法;Servlet 的doGet()
,doPost()
是模板方法的變體;構建工具(Maven, Gradle)的生命周期是模板方法的宏觀體現。未來趨勢是:模板方法將與函數式編程融合,模板方法接受函數式接口作為步驟(如 Java 8 的
Consumer
,Function
);在低代碼/無代碼平臺中,可視化流程設計器生成模板方法代碼;在AI 工作流中,AI Agent 的“規劃-執行-反思”循環可建模為模板方法;在量子軟件中,量子算法的通用步驟(初始化、操作、測量)可定義為模板。掌握模板方法模式,是設計可復用框架、標準化系統的核心能力。作為架構師,應在設計任何需要“統一流程、定制細節”的模塊時,優先考慮模板方法模式。它不僅是模式,更是系統秩序的基石——它用不變的骨架約束變化的細節,用父類的權威保障流程的正確,用子類的自由激發實現的創新,從而構建出既穩定又靈活、既統一又多樣的軟件生態系統。