模板方法模式是一种行为设计模式,它定义一个操作中的算法骨架,将一些步骤延迟到子类中实现。核心思想是"封装不变部分,扩展可变部分",即父类确定流程框架,子类根据需求重写特定步骤,而不改变整体流程结构。
介绍
核心角色
- 抽象类(Abstract Class):定义算法骨架(模板方法),包含多个抽象方法(子类需实现)和可选的钩子方法(Hook Method,子类可选择性重写)。
- 具体子类(Concrete Class):继承抽象类,实现抽象方法,完成算法中特定于子类的步骤。
优点
- 封装性好:算法骨架由父类控制,子类无需关心整体流程,只需专注于自身步骤实现。
- 扩展性强:新增子类时,只需重写特定方法即可扩展新功能,符合"开闭原则"。
- 代码复用:公共步骤在父类中实现,避免重复编码。
- 便于维护:流程变更时只需修改父类的模板方法,无需修改所有子类。
模板方法模式通过分离"不变的流程"和"可变的步骤",实现了代码复用和灵活扩展,特别适合构建框架或统一多子类的核心流程。其核心是父类定骨架,子类填细节,在保证流程一致性的同时,最大化子类的灵活性。
适用场景
流程固定但步骤实现可变
当多个子类的核心流程完全一致,仅部分步骤的实现不同时(如框架中的初始化流程、测试流程)。需要控制子类扩展
父类通过模板方法固定算法骨架,子类只能通过重写特定方法扩展,避免流程被随意修改(如final
修饰模板方法)。代码复用
将公共步骤(如示例中的boilWater
)在父类中实现,减少子类重复代码。
实现
以咖啡和茶的冲泡流程为例,两者的核心步骤相似(烧水→冲泡→倒杯→加调料),但具体实现不同,适合用模板方法模式统一流程。
#include <iostream>
#include <string>
// 抽象类:定义冲泡流程的模板方法
class Beverage {
public:
// 模板方法:定义算法骨架(不可被子类修改)
void prepareRecipe() final {
boilWater(); // 烧水(公共步骤)
brew(); // 冲泡(子类实现)
pourInCup(); // 倒杯(公共步骤)
if (customerWantsCondiments()) { // 钩子方法:判断是否加调料
addCondiments(); // 加调料(子类实现)
}
}
protected:
// 抽象方法:子类必须实现
virtual void brew() = 0;
virtual void addCondiments() = 0;
// 具体方法:父类实现,子类共享
void boilWater() {
std::cout << "烧开水(100℃)" << std::endl;
}
void pourInCup() {
std::cout << "倒入杯子中" << std::endl;
}
// 钩子方法:默认返回true,子类可重写(可选步骤)
virtual bool customerWantsCondiments() {
return true;
}
};
// 具体子类:咖啡
class Coffee : public Beverage {
protected:
// 实现抽象方法:冲泡咖啡
void brew() override {
std::cout << "用沸水冲泡咖啡粉" << std::endl;
}
// 实现抽象方法:加调料
void addCondiments() override {
std::cout << "添加糖和牛奶" << std::endl;
}
// 重写钩子方法:询问用户是否加调料
bool customerWantsCondiments() override {
std::string answer;
std::cout << "请问需要加奶和糖吗?(y/n)";
std::cin >> answer;
return answer == "y" || answer == "Y";
}
};
// 具体子类:茶
class Tea : public Beverage {
protected:
// 实现抽象方法:泡茶
void brew() override {
std::cout << "用沸水浸泡茶叶" << std::endl;
}
// 实现抽象方法:加调料
void addCondiments() override {
std::cout << "添加柠檬" << std::endl;
}
// 不重写钩子方法,使用父类默认实现(默认加调料)
};
int main() {
std::cout << "=== 制作咖啡 ===" << std::endl;
Beverage* coffee = new Coffee();
coffee->prepareRecipe(); // 调用模板方法,执行完整流程
std::cout << "\n=== 制作茶 ===" << std::endl;
Beverage* tea = new Tea();
tea->prepareRecipe();
delete coffee;
delete tea;
return 0;
}
输出结果
=== 制作咖啡 ===
烧开水(100℃)
用沸水冲泡咖啡粉
倒入杯子中
请问需要加奶和糖吗?(y/n)y
添加糖和牛奶
=== 制作茶 ===
烧开水(100℃)
用沸水浸泡茶叶
倒入杯子中
添加柠檬
优点
钩子方法增强灵活性
- 父类
Beverage
中的customerWantsCondiments()
是钩子方法,默认返回true
,子类可根据需求重写(如Coffee
类通过用户输入决定是否加调料)。 - 解决了"强制子类实现不需要的步骤"的问题,符合"最小知识原则"。
- 父类
模板方法不可篡改
- 用
final
修饰prepareRecipe()
,确保子类无法修改整体流程(如不能跳过"烧水"步骤),保证框架的稳定性。
- 用
典型应用场景
框架设计
- 框架(如MFC、Spring)定义核心流程(如
InitApplication
),用户通过重写钩子方法扩展功能。 - 测试框架(如JUnit)的
setUp()
和tearDown()
方法,固定测试流程,用户实现具体测试逻辑。
- 框架(如MFC、Spring)定义核心流程(如
文档生成工具
- 统一文档结构(标题→摘要→正文→结论),子类实现不同类型文档(如报告、论文)的具体内容。
游戏开发
- 角色技能释放流程(前摇→施法→伤害计算→后摇),不同技能重写"伤害计算"步骤。
资源加载
- 资源加载流程(检查缓存→读取文件→解析数据→缓存),不同类型资源(图片、音频)重写"解析数据"步骤。
优化
#include <iostream>
#include <string>
#include <functional>
// 抽象基类:定义冲泡流程模板
class Beverage {
public:
// 模板方法:用final禁止子类重写,保证流程固定
void prepareRecipe() final {
try {
boilWater(); // 公共步骤:烧水
brew(); // 抽象步骤:冲泡(子类实现)
pourInCup(); // 公共步骤:倒杯
if (customerWantsCondiments()) { // 钩子方法:是否加调料
addCondiments(); // 抽象步骤:加调料(子类实现)
}
} catch (const std::exception& e) {
// 统一异常处理:避免子类重复编写
std::cerr << "制作失败:" << e.what() << std::endl;
}
}
protected:
// 抽象方法:子类必须实现
virtual void brew() = 0;
virtual void addCondiments() = 0;
// 公共方法:父类实现,子类共享
void boilWater() {
std::cout << "烧开水(100℃)" << std::endl;
}
void pourInCup() {
std::cout << "倒入杯子中" << std::endl;
}
// 钩子方法:子类可选择性重写(默认返回true)
virtual bool customerWantsCondiments() {
return true; // 默认加调料
}
// 析构函数设为虚函数,确保子类正确析构
virtual ~Beverage() = default;
};
class Coffee : public Beverage {
protected:
// 实现抽象方法:冲泡咖啡
void brew() override {
std::cout << "用沸水冲泡咖啡粉" << std::endl;
}
// 实现抽象方法:加调料
void addCondiments() override {
std::cout << "添加糖和牛奶" << std::endl;
}
// 重写钩子方法:询问用户是否需要调料
bool customerWantsCondiments() override {
std::string answer;
std::cout << "请问需要加奶和糖吗?(y/n)";
std::cin >> answer;
return answer == "y" || answer == "Y";
}
};
class Tea : public Beverage {
public:
// 支持动态设置加调料的方式(策略模式结合)
using CondimentFunc = std::function<void()>;
void setCondimentFunc(CondimentFunc func) {
condimentFunc = func;
}
protected:
void brew() override {
std::cout << "用沸水浸泡茶叶" << std::endl;
}
// 调用动态设置的调料函数
void addCondiments() override {
if (condimentFunc) {
condimentFunc();
} else {
std::cout << "添加蜂蜜" << std::endl; // 默认
}
}
// 钩子方法:默认不加调料(覆盖父类行为)
bool customerWantsCondiments() override {
return true; // 茶默认加调料
}
private:
CondimentFunc condimentFunc; // 函数对象:动态步骤
};
int main() {
std::cout << "=== 制作咖啡 ===" << std::endl;
Beverage* coffee = new Coffee();
coffee->prepareRecipe(); // 会询问是否加调料
std::cout << "\n=== 制作柠檬茶 ===" << std::endl;
Tea* tea = new Tea();
// 动态设置加柠檬(无需创建新子类)
tea->setCondimentFunc([](){
std::cout << "添加柠檬片" << std::endl;
});
tea->prepareRecipe();
std::cout << "\n=== 制作奶茶 ===" << std::endl;
Tea* milkTea = new Tea();
// 动态设置加牛奶(策略模式)
milkTea->setCondimentFunc([](){
std::cout << "添加牛奶和珍珠" << std::endl;
});
milkTea->prepareRecipe();
delete coffee;
delete tea;
delete milkTea;
return 0;
}
优化点
动态步骤替换(策略模式结合)
Tea
类通过std::function
允许动态设置addCondiments()
的实现(如柠檬茶、奶茶),无需创建多个子类,减少代码冗余。- 适合步骤实现频繁变化的场景,比单纯继承更灵活。
统一异常处理
- 模板方法中添加
try-catch
,子类无需单独处理异常,降低重复代码,且便于全局错误日志收集。
- 模板方法中添加
优化后的适用场景
- 框架设计:需要严格控制核心流程,但允许用户灵活扩展部分步骤(如游戏引擎的渲染流程)。
- 动态配置:步骤实现可能频繁变化(如不同用户的个性化需求),通过函数对象动态切换比创建子类更高效。
- 异常敏感场景:需要统一错误处理的系统(如金融交易流程),避免子类遗漏异常处理。