模板方法模式(Template Method Pattern)是一种行为设计模式,它定义了一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
模板方法模式的主要特点包括:
1. 算法骨架
模板方法模式在父类中定义算法的骨架,并在子类中实现具体的步骤。这样,算法的整体结构是固定的,但具体的实现可以由子类决定。
2. 复用父类代码
通过在父类中实现算法的通用部分,可以避免代码重复,子类只需要实现特定的步骤,从而实现代码复用。
3. 扩展灵活性
子类可以通过实现或重写特定的步骤来扩展算法,而不需要修改父类的代码,从而符合开闭原则(对扩展开放,对修改关闭)。
示例
假设我们有一个制作饮料的过程,制作饮料的步骤包括煮水、泡饮料、倒入杯子和添加调料。不同的饮料可能在泡饮料和添加调料的步骤上有所不同。我们可以使用模板方法模式来实现这一过程。
#include <iostream>
// 制作饮料的基类
class Beverage {
public:
// 模板方法,定义了制作饮料的算法骨架
void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
virtual ~Beverage() {}
protected:
// 固定的步骤
void boilWater() {
std::cout << "Boiling water" << std::endl;
}
void pourInCup() {
std::cout << "Pouring into cup" << std::endl;
}
// 需要子类实现的步骤
virtual void brew() = 0;
virtual void addCondiments() = 0;
};
// 具体子类:茶
class Tea : public Beverage {
protected:
void brew() override {
std::cout << "Steeping the tea" << std::endl;
}
void addCondiments() override {
std::cout << "Adding lemon" << std::endl;
}
};
// 具体子类:咖啡
class Coffee : public Beverage {
protected:
void brew() override {
std::cout << "Dripping coffee through filter" << std::endl;
}
void addCondiments() override {
std::cout << "Adding sugar and milk" << std::endl;
}
};
// 主函数测试
int main() {
Tea tea;
Coffee coffee;
std::cout << "Making tea..." << std::endl;
tea.prepareRecipe();
std::cout << "\nMaking coffee..." << std::endl;
coffee.prepareRecipe();
return 0;
}
解释
-
Beverage:这是一个抽象基类,它定义了制作饮料的模板方法
prepareRecipe
,以及一些固定的步骤(boilWater
和pourInCup
)。此外,它还定义了两个纯虚函数(brew
和addCondiments
),这些函数需要在子类中实现。 -
Tea和Coffee:这两个类是具体的子类,它们实现了父类中定义的抽象方法。
Tea
类实现了泡茶和加柠檬的方法,Coffee
类实现了滴滤咖啡和加糖加奶的方法。 -
主函数:
- 创建
Tea
和Coffee
的实例。 - 分别调用
prepareRecipe
方法,这将触发整个制作饮料的过程。
- 创建
总结
模板方法模式通过在父类中定义算法的骨架,并将具体的实现步骤延迟到子类中,实现了代码的复用和算法的扩展。它适用于有固定算法结构,但其中某些步骤需要由子类具体实现的情况。模板方法模式使得算法的整体结构保持不变,而具体的实现细节可以灵活变化。
补充
在模板方法模式中加入钩子(Hook)可以让子类在不完全覆盖父类方法的情况下,插入特定的行为。钩子通常是一个在基类中定义的方法,它的默认实现是空的或返回一个默认值。子类可以根据需要选择性地覆盖这个钩子方法,以实现特定的行为。
示例:在饮料制作过程中加入钩子
在前面的示例中,我们可以为饮料制作过程加入一个钩子,以便在特定情况下让子类选择是否添加调料。
#include <iostream>
// 制作饮料的基类
class Beverage {
public:
// 模板方法,定义了制作饮料的算法骨架
void prepareRecipe() {
boilWater();
brew();
pourInCup();
if (customerWantsCondiments()) { // 调用钩子方法
addCondiments();
}
}
virtual ~Beverage() {}
protected:
// 固定的步骤
void boilWater() {
std::cout << "Boiling water" << std::endl;
}
void pourInCup() {
std::cout << "Pouring into cup" << std::endl;
}
// 需要子类实现的步骤
virtual void brew() = 0;
virtual void addCondiments() = 0;
// 钩子方法,子类可以覆盖这个方法以添加特定行为
virtual bool customerWantsCondiments() {
return true; // 默认情况下添加调料
}
};
// 具体子类:茶
class Tea : public Beverage {
protected:
void brew() override {
std::cout << "Steeping the tea" << std::endl;
}
void addCondiments() override {
std::cout << "Adding lemon" << std::endl;
}
// 覆盖钩子方法,提供自己的实现
bool customerWantsCondiments() override {
return getUserInput();
}
private:
bool getUserInput() {
char answer;
std::cout << "Would you like lemon with your tea (y/n)? ";
std::cin >> answer;
return (answer == 'y' || answer == 'Y');
}
};
// 具体子类:咖啡
class Coffee : public Beverage {
protected:
void brew() override {
std::cout << "Dripping coffee through filter" << std::endl;
}
void addCondiments() override {
std::cout << "Adding sugar and milk" << std::endl;
}
// 覆盖钩子方法,提供自己的实现
bool customerWantsCondiments() override {
return getUserInput();
}
private:
bool getUserInput() {
char answer;
std::cout << "Would you like sugar and milk with your coffee (y/n)? ";
std::cin >> answer;
return (answer == 'y' || answer == 'Y');
}
};
// 主函数测试
int main() {
Tea tea;
Coffee coffee;
std::cout << "Making tea..." << std::endl;
tea.prepareRecipe();
std::cout << "\nMaking coffee..." << std::endl;
coffee.prepareRecipe();
return 0;
}
解释
-
Beverage:在基类中,我们增加了一个钩子方法
customerWantsCondiments
,默认实现是返回true
。在prepareRecipe
模板方法中,我们在添加调料的步骤前调用了这个钩子方法,根据返回值决定是否添加调料。 -
Tea和Coffee:在这两个子类中,我们覆盖了钩子方法
customerWantsCondiments
,并实现了自己的用户交互逻辑,询问用户是否需要添加调料。 -
主函数:
- 创建
Tea
和Coffee
的实例。 - 调用
prepareRecipe
方法制作饮料,并根据用户输入决定是否添加调料。
- 创建
总结
通过在模板方法模式中加入钩子,可以在不改变算法结构的情况下,让子类有机会在特定的步骤上添加自定义行为。钩子提供了一种灵活的机制,使得子类可以选择性地扩展或修改父类的算法,而不需要完全覆盖父类的方法。这增强了模板方法模式的灵活性和可扩展性。