模板方法模式C++
模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个算法的骨架,将某些步骤延迟到子类中实现。这种模式让子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤,从而实现代码复用和扩展。
模板方法模式的核心角色
- AbstractClass(抽象类):定义算法的骨架(模板方法),包含多个抽象方法(步骤)和可能的钩子方法
- ConcreteClass(具体子类):实现抽象类中的抽象方法,完成算法中特定于子类的步骤
C++实现示例
以下以"饮料制作流程"为例实现模板方法模式,冲泡咖啡和泡茶的流程相似(都需要煮水、冲泡、倒杯、加调料),但具体步骤存在差异:
#include <iostream>
#include <string>// 抽象类:饮料
class Beverage {
public:// 模板方法:定义算法骨架,声明为final防止子类重写void prepareRecipe() final {boilWater(); // 步骤1:煮水brew(); // 步骤2:冲泡(抽象方法,子类实现)pourInCup(); // 步骤3:倒杯if (customerWantsCondiments()) { // 钩子方法:判断是否需要加调料addCondiments(); // 步骤4:加调料(抽象方法,子类实现)}}// 抽象方法:冲泡(子类实现)virtual void brew() = 0;// 抽象方法:加调料(子类实现)virtual void addCondiments() = 0;// 具体方法:煮水(所有饮料通用)void boilWater() {std::cout << "煮水" << std::endl;}// 具体方法:倒杯(所有饮料通用)void pourInCup() {std::cout << "倒入杯子中" << std::endl;}// 钩子方法:默认返回true(需要加调料),子类可重写virtual bool customerWantsCondiments() {return true;}// 虚析构函数virtual ~Beverage() = default;
};// 具体子类:咖啡
class Coffee : public Beverage {
public:// 实现冲泡步骤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:// 实现冲泡步骤void brew() override {std::cout << "用沸水浸泡茶叶" << std::endl;}// 实现加调料步骤void addCondiments() override {std::cout << "加入柠檬" << std::endl;}// 重写钩子方法:默认不加调料bool customerWantsCondiments() override {return false; // 茶默认不加柠檬}
};// 客户端代码
void makeBeverage(Beverage* beverage) {std::cout << "\n开始制作饮料..." << std::endl;beverage->prepareRecipe();std::cout << "饮料制作完成" << std::endl;
}int main() {// 制作咖啡Beverage* coffee = new Coffee();makeBeverage(coffee);// 制作茶Beverage* tea = new Tea();makeBeverage(tea);// 清理资源delete coffee;delete tea;return 0;
}
代码解析
-
模板方法(
prepareRecipe
):- 定义了饮料制作的固定流程(煮水→冲泡→倒杯→加调料),用
final
修饰防止子类修改算法结构 - 其中
boilWater
和pourInCup
是通用步骤(具体方法),brew
和addCondiments
是抽象步骤(由子类实现)
- 定义了饮料制作的固定流程(煮水→冲泡→倒杯→加调料),用
-
抽象方法(
brew
、addCondiments
):- 声明为纯虚函数,强制子类实现,这些步骤是算法中因具体类型而异的部分
- 咖啡实现为"冲泡咖啡粉"和"加糖牛奶",茶实现为"浸泡茶叶"和"加柠檬"
-
钩子方法(
customerWantsCondiments
):- 提供默认实现(返回
true
),子类可根据需要重写 - 咖啡重写为询问用户是否加调料,茶重写为默认不加调料,增强了算法的灵活性
- 提供默认实现(返回
-
子类扩展:
- 子类通过实现抽象方法定制特定步骤,无需修改算法整体结构
- 钩子方法允许子类有选择地参与算法,而非强制实现所有步骤
模板方法模式的优缺点
优点:
- 封装不变部分,扩展可变部分,实现代码复用(通用步骤在父类实现)
- 子类专注于实现特定步骤,无需关心算法整体结构
- 通过钩子方法提供灵活的扩展点,增强了适应性
- 符合开放-封闭原则(扩展新子类无需修改父类)
缺点:
- 父类中的抽象方法过多时,会导致子类实现负担加重
- 算法骨架被固定在父类,如需修改整体流程需修改父类,违反开放-封闭原则
- 可能增加系统复杂度(引入多个子类)
适用场景
- 当多个子类存在公共行为(算法步骤),且这些行为的结构相同但细节不同时
- 当需要控制子类扩展时(通过钩子方法限制扩展范围)
- 当希望在不改变算法结构的前提下,允许子类定制某些步骤时
常见应用:
- 框架中的生命周期方法(如Servlet的
init()
、service()
、destroy()
) - 工具类中的算法模板(如排序算法的骨架,具体比较逻辑由子类实现)
- 测试框架中的测试用例模板(固定测试流程,具体测试步骤由子类实现)
模板方法模式与策略模式的区别:模板方法通过继承实现算法步骤的定制,强调"继承式扩展";策略模式通过组合实现算法的替换,强调"组合式扩展"。