C++设计模式:模板方法模式(饮品制作)
·
什么是模板方法模式?
模板方法模式是一种行为型设计模式,它定义了一个操作的骨架(算法框架),将某些具体步骤的实现延迟到子类中,从而使得子类可以重新定义算法的某些步骤,而不改变算法的结构。
通俗解释:
假设我们需要泡一杯茶或者冲一杯咖啡,步骤大致是:
- 煮水。
- 冲泡茶叶或咖啡。
- 倒入杯中。
- 加配料(比如加糖、加牛奶)。
这些步骤中,煮水和倒入杯中是固定的,属于模板,而冲泡内容(茶或咖啡)和加配料则可以根据具体情况进行定制。
模板方法模式的核心就是定义一个模板方法(固定步骤的框架),让子类来实现某些特定步骤的逻辑。
模板方法模式的特点
- 固定算法的骨架:模板方法定义了算法的固定流程。
- 可扩展:子类可以实现特定的步骤,而无需改变算法的整体结构。
- 遵循开闭原则:算法框架对扩展开放,但对修改封闭。
模板方法模式的结构
UML 类图
+----------------------+
| AbstractClass | // 抽象类
+----------------------+
| + templateMethod() | // 模板方法(定义流程)
| + step1() | // 具体方法(固定实现)
| + step2() = 0 | // 抽象方法(延迟实现)
+----------------------+
^
|
+----------------------+
| ConcreteClass | // 具体实现类
+----------------------+
| + step2() | // 实现抽象方法
+----------------------+
组成部分:
-
AbstractClass(抽象类)- 定义算法的骨架。
- 包含一个
templateMethod模板方法,描述操作的固定流程。 - 包含一些具体方法(已实现)和抽象方法(需子类实现)。
-
ConcreteClass(具体实现类)- 实现
AbstractClass中的抽象方法。 - 可以对模板方法中的特定步骤进行定制。
- 实现
-
templateMethod(模板方法)- 在抽象类中定义,用于描述算法的整体流程。
模板方法模式的优点和缺点
优点:
- 代码复用:将通用的代码放在抽象类中,减少重复代码。
- 灵活性高:允许子类通过实现特定的步骤来扩展功能。
- 符合开闭原则:算法骨架固定,子类可以自由扩展具体步骤。
缺点:
- 继承限制:需要通过继承来实现,可能会导致类的数量增加。
- 灵活性有限:模板方法的框架是固定的,若需要大幅更改流程,则需要修改抽象类。
案例:饮品制作(冲咖啡与泡茶)
需求描述
我们需要设计一个饮品制作流程,支持以下两种饮品:
- 咖啡
- 茶
制作饮品的流程是固定的,但某些步骤(如冲泡和加配料)根据具体饮品有所不同。
代码实现
以下是完整的代码实现。
#include <iostream>
#include <string>
// 抽象类:饮品
class Beverage {
public:
// 模板方法:定义制作饮品的流程
void prepareRecipe() {
boilWater(); // 煮水(固定步骤)
brew(); // 冲泡(具体饮品不同)
pourInCup(); // 倒入杯中(固定步骤)
addCondiments(); // 加配料(具体饮品不同)
}
protected:
// 固定步骤:煮水
void boilWater() {
std::cout << "将水煮沸。" << std::endl;
}
// 固定步骤:倒入杯中
void pourInCup() {
std::cout << "将饮品倒入杯中。" << std::endl;
}
// 抽象步骤:冲泡(需要子类实现)
virtual void brew() = 0;
// 抽象步骤:加配料(需要子类实现)
virtual void addCondiments() = 0;
// 虚析构函数,保证子类正确释放资源
virtual ~Beverage() = default;
};
// 具体类:咖啡
class Coffee : public Beverage {
protected:
// 实现冲泡步骤
void brew() override {
std::cout << "用沸水冲泡咖啡。" << std::endl;
}
// 实现加配料步骤
void addCondiments() override {
std::cout << "加入糖和牛奶。" << std::endl;
}
};
// 具体类:茶
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;
Coffee coffee;
coffee.prepareRecipe();
std::cout << "\n制作茶:" << std::endl;
Tea tea;
tea.prepareRecipe();
return 0;
}
运行结果
运行以上代码后,输出如下:
制作咖啡:
将水煮沸。
用沸水冲泡咖啡。
将饮品倒入杯中。
加入糖和牛奶。
制作茶:
将水煮沸。
用沸水浸泡茶叶。
将饮品倒入杯中。
加入柠檬。
代码讲解
1. 抽象类(Beverage)
class Beverage {
public:
void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
protected:
void boilWater() {
std::cout << "将水煮沸。" << std::endl;
}
void pourInCup() {
std::cout << "将饮品倒入杯中。" << std::endl;
}
virtual void brew() = 0; // 冲泡(抽象方法)
virtual void addCondiments() = 0; // 加配料(抽象方法)
};
- **
prepareRecipe**是模板方法,定义了制作饮品的固定流程。 - **
boilWater和pourInCup**是固定步骤,所有子类都共享。 - **
brew和addCondiments**是抽象步骤,需要具体子类实现。
2. 具体类(Coffee 和 Tea)
class Coffee : public Beverage {
protected:
void brew() override {
std::cout << "用沸水冲泡咖啡。" << std::endl;
}
void addCondiments() override {
std::cout << "加入糖和牛奶。" << std::endl;
}
};
Coffee类实现了brew和addCondiments方法,用于描述咖啡的具体冲泡和加配料逻辑。Tea类则实现了适合茶的冲泡和加配料逻辑。
3. 客户端代码
Coffee coffee;
coffee.prepareRecipe();
Tea tea;
tea.prepareRecipe();
- 客户端创建
Coffee和Tea对象,并调用模板方法prepareRecipe来完成饮品制作。
模板方法模式的优缺点
优点:
- 复用固定流程代码:将通用的逻辑写入抽象类,减少代码重复。
- 灵活扩展:子类只需实现自己的步骤逻辑,无需修改模板方法的整体流程。
- 符合开闭原则:算法框架固定,对修改封闭;具体步骤对扩展开放。
缺点:
- 继承关系限制:模板方法模式依赖继承,如果需要使用多个模板,可能导致继承关系复杂。
- 细节依赖抽象类:所有具体类都必须依赖抽象类,增加了类的依赖关系。
适用场景
- 需要复用固定流程的代码:
- 比如算法的固定步骤(数据清洗、训练、测试等)。
- 不同子类有不同实现的场景:
- 比如文件解析器,整体流程是固定的,但具体解析逻辑可能不同。
- 复杂流程的抽象:
- 比如生产流水线,流程固定但具体实现由不同生产线决定。
总结
模板方法模式是一个非常经典的设计模式,适合用在需要固定流程且可扩展步骤的场景中。在本案例中,我们通过模板方法模式实现了饮品的制作流程,充分展示了其固定框架+灵活扩展的特点。
模板方法模式的核心在于封装不变部分,扩展可变部分,是一种非常实用且高效的设计模式!****
更多推荐


所有评论(0)