目錄
前言
一、模板方法模式的核心思想
二、模板方法模式的結構組成
1. 抽象類(Abstract Class)
2. 具體子類(Concrete Class)
三、C++ 實現示例:咖啡與茶的制作流程
步驟 1:定義抽象類(飲料基類)
步驟 2:實現具體子類(咖啡和茶)
步驟 3:測試代碼
四、模板方法模式的優缺點(C++ 視角)
優點
缺點
五、C++ 中模板方法模式的應用場景
六、C++ 中模板方法模式的注意事項
七、總結
前言
在面向對象設計中,我們經常會遇到這樣的場景:多個類實現相似的流程,但部分步驟存在差異。如果為每個類重復編寫相同的流程代碼,不僅會導致冗余,還會增加維護成本。模板方法模式正是為解決這類問題而生的 —— 它通過定義統一的流程骨架,將可變步驟延遲到子類實現,完美平衡了流程標準化與細節差異化。
本文將結合 C++ 代碼,深入解析模板方法模式的原理、實現與應用。
一、模板方法模式的核心思想
模板方法模式是一種經典的行為型設計模式,其核心可以概括為:“定義算法骨架,延遲具體實現”。
想象一個場景:制作咖啡和茶的流程高度相似(煮水→沖泡→裝杯→加調料),但 “沖泡” 和 “加調料” 的具體操作不同。模板方法模式會將這些相同的流程步驟抽象到父類,而將不同的步驟聲明為抽象方法,由子類(咖啡、茶)各自實現。
用 C++ 的視角來看,這種模式通過抽象類 + 繼承實現:
- 抽象類負責定義 “流程骨架”(模板方法);
- 子類負責實現 “可變細節”(抽象方法)。
二、模板方法模式的結構組成
模板方法模式主要包含兩個角色,在 C++ 中通常通過類層次結構實現:
1. 抽象類(Abstract Class)
- 模板方法(Template Method):一個非虛成員函數(通常用
final
修飾,防止子類重寫),定義算法的步驟順序,調用其他方法(包括抽象方法和具體方法)。 - 抽象方法(Primitive Operations):純虛函數,由子類實現的可變步驟。
- 具體方法(Concrete Methods):普通成員函數,算法中固定不變的步驟(子類無需修改)。
- 鉤子方法(Hook Methods):虛函數(有默認實現),子類可選擇性重寫,用于控制模板流程的分支(可選)。
2. 具體子類(Concrete Class)
- 繼承抽象類,實現所有純虛的抽象方法;
- 可選重寫鉤子方法,調整流程的執行邏輯。
三、C++ 實現示例:咖啡與茶的制作流程
下面我們用 C++ 實現一個經典案例:咖啡和茶的制作。兩者的流程相似,但 “沖泡” 和 “加調料” 的步驟不同,適合用模板方法模式封裝。
步驟 1:定義抽象類(飲料基類)
首先創建抽象類Beverage
,它包含制作飲料的流程骨架和相關方法:
// Beverage.h
#ifndef BEVERAGE_H
#define BEVERAGE_H#include <iostream>
using namespace std;class Beverage {
public:// 模板方法:定義制作流程的骨架,用final防止子類修改(C++11及以上支持)void prepareRecipe() final {boilWater(); // 固定步驟:煮水brew(); // 抽象方法:沖泡(子類實現)pourInCup(); // 固定步驟:倒入杯子addCondiments(); // 抽象方法:加調料(子類實現)if (customerWantsCondiments()) { // 鉤子方法:判斷是否加額外調料addExtraCondiments(); // 可選步驟:加額外調料}}protected:// 抽象方法:沖泡(純虛函數,子類必須實現)virtual void brew() = 0;// 抽象方法:加調料(純虛函數,子類必須實現)virtual void addCondiments() = 0;// 鉤子方法:默認返回true(加額外調料),子類可重寫virtual bool customerWantsCondiments() {return true;}private:// 具體方法:固定步驟——煮水(子類無需修改)void boilWater() {cout << "煮水至沸騰" << endl;}// 具體方法:固定步驟——倒入杯子(子類無需修改)void pourInCup() {cout << "倒入杯中" << endl;}// 具體方法:可選步驟——加額外調料(固定邏輯)void addExtraCondiments() {cout << "添加額外糖/奶" << endl;}
};#endif // BEVERAGE_H
代碼說明:
prepareRecipe()
是模板方法,用final
確保子類無法修改流程順序;brew()
和addCondiments()
是純虛函數(抽象方法),必須由子類實現;customerWantsCondiments()
是鉤子方法,提供默認實現,子類可重寫以改變流程分支;boilWater()
、pourInCup()
等是具體方法,封裝固定步驟,子類無需關心。
步驟 2:實現具體子類(咖啡和茶)
接下來創建Coffee
和Tea
類,繼承Beverage
并實現抽象方法:
Coffee
// Coffee.h
#ifndef COFFEE_H
#define COFFEE_H#include "Beverage.h"class Coffee : public Beverage {
protected:// 實現抽象方法:咖啡的沖泡邏輯void brew() override {cout << "用沸水沖泡咖啡粉" << endl;}// 實現抽象方法:咖啡的加調料邏輯void addCondiments() override {cout << "加牛奶和糖" << endl;}// 重寫鉤子方法:咖啡默認不加額外調料bool customerWantsCondiments() override {return false; // 不執行addExtraCondiments()}
};#endif // COFFEE_H
Tea
// Tea.h
#ifndef TEA_H
#define TEA_H#include "Beverage.h"class Tea : public Beverage {
protected:// 實現抽象方法:茶的沖泡邏輯void brew() override {cout << "用沸水浸泡茶葉" << endl;}// 實現抽象方法:茶的加調料邏輯void addCondiments() override {cout << "加檸檬" << endl;}// 不重寫鉤子方法,使用父類默認邏輯(加額外調料)
};#endif // TEA_H
代碼說明:
Coffee
和Tea
分別實現了brew()
和addCondiments()
,定義各自的差異化步驟;Coffee
重寫了鉤子方法customerWantsCondiments()
,返回false
以跳過 “加額外調料” 步驟;Tea
未重寫鉤子方法,使用父類默認實現(返回true
),因此會執行 “加額外調料”。
步驟 3:測試代碼
最后編寫測試代碼,驗證模板方法的執行效果:
// main.cpp
#include "Coffee.h"
#include "Tea.h"int main() {// 制作咖啡Beverage* coffee = new Coffee();cout << "制作咖啡:" << endl;coffee->prepareRecipe();// 制作茶Beverage* tea = new Tea();cout << "\n制作茶:" << endl;tea->prepareRecipe();// 釋放資源delete coffee;delete tea;return 0;
}
輸出結果:
制作咖啡:
煮水至沸騰
用沸水沖泡咖啡粉
倒入杯中
加牛奶和糖制作茶:
煮水至沸騰
用沸水浸泡茶葉
倒入杯中
加檸檬
添加額外糖/奶
結果分析:
- 咖啡和茶都遵循了
prepareRecipe()
定義的流程骨架; - 差異化步驟(
brew()
、addCondiments()
)按子類實現執行; - 鉤子方法控制了流程分支:咖啡跳過 “加額外調料”,茶則執行了該步驟。
四、模板方法模式的優缺點(C++ 視角)
優點
- 代碼復用:將公共流程(如
boilWater()
)抽取到抽象類,減少重復代碼; - 符合開閉原則:新增飲料(如奶茶)只需繼承
Beverage
并實現抽象方法,無需修改父類; - 流程可控:父類通過
final
修飾模板方法,避免子類破壞流程邏輯; - 邏輯清晰:算法的核心步驟在父類中集中體現,便于維護。
缺點
- 類數量膨脹:每增加一個具體實現就需要一個子類,可能導致類數量過多;
- 繼承局限性:C++ 不支持多繼承,子類若需復用多個模板流程會受限制;
- 耦合性:子類與父類的依賴較強,父類的修改可能影響所有子類。
五、C++ 中模板方法模式的應用場景
-
框架設計
C++ 框架常通過模板方法模式定義核心流程,讓用戶通過子類定制細節。例如:- MFC 框架的
Run()
方法:定義了消息循環的骨架,用戶重寫OnDraw()
等方法處理具體消息; - 測試框架(如 Google Test):
Test
類定義了測試用例的執行流程,用戶實現SetUp()
和TearDown()
定制前后置操作。
- MFC 框架的
-
業務流程標準化
當多個業務場景共享相似流程但細節不同時,例如:- 支付流程(創建訂單→驗證→扣款→回調):不同支付方式(微信 / 支付寶)的 “扣款” 步驟不同;
- 數據處理流程(讀取→解析→過濾→存儲):不同數據格式(JSON/XML)的 “解析” 步驟不同。
-
鉤子方法的靈活使用
需要根據子類特性動態調整流程時,例如:- 日志框架:通過鉤子方法控制是否輸出調試日志;
- 游戲 AI:通過鉤子方法決定角色在特定條件下的行為分支。
六、C++ 中模板方法模式的注意事項
-
模板方法用
final
修飾
C++11 及以上支持final
關鍵字,修飾模板方法可防止子類意外修改流程順序,例如:void prepareRecipe() final { ... } // 禁止子類重寫
-
抽象方法與鉤子方法的區分
- 必須實現的步驟用純虛函數(
virtual void func() = 0
); - 可選重寫的步驟用虛函數(提供默認實現),即鉤子方法。
- 必須實現的步驟用純虛函數(
-
避免過度設計
若流程差異較小,不必強行使用模板方法模式,否則可能增加代碼復雜度。
七、總結
模板方法模式是 C++ 中實現 “流程復用 + 細節定制” 的經典模式,它通過抽象類定義算法骨架,用子類填充可變細節,既保證了流程的一致性,又保留了靈活性。
在 C++ 開發中,合理使用模板方法模式可以顯著減少重復代碼,提高系統的可維護性。但需注意平衡繼承帶來的耦合性,必要時可結合策略模式(通過組合實現)彌補繼承的局限性。
掌握模板方法模式,不僅能寫出更優雅的 C++ 代碼,更能理解許多 C++ 框架的設計思想 ——“骨架由框架定,細節由開發者填”。