模板方法模式
本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 99 课,共 104 课。
模板方法模式在基类中定义算法的框架,让子类可以重写特定步骤,而不改变算法的整体结构。与替换整个算法的策略模式不同,模板方法模式固定算法,同时允许自定义各个步骤。
基类实现模板方法(算法),并调用子类必须提供的抽象方法或虚方法:
#include <iostream>
class DataProcessor {
public:
// 模板方法:定义算法的基本框架
void process() {
loadData();
processData();
saveResults();
}
virtual ~DataProcessor() = default;
protected:
virtual void loadData() = 0; // 必须实现
virtual void processData() = 0; // 必须实现
// 钩子方法:可选择重写,默认已有实现
virtual void saveResults() {
std::cout << "Saving to default location\n";
}
};
class CSVProcessor : public DataProcessor {
protected:
void loadData() override {
std::cout << "Loading CSV file\n";
}
void processData() override {
std::cout << "Parsing CSV data\n";
}
};
class JSONProcessor : public DataProcessor {
protected:
void loadData() override {
std::cout << "Loading JSON file\n";
}
void processData() override {
std::cout << "Parsing JSON data\n";
}
void saveResults() override {
std::cout << "Saving to cloud storage\n";
}
};
int main() {
CSVProcessor csv;
csv.process(); // 使用默认的 saveResults
JSONProcessor json;
json.process(); // 使用自定义的 saveResults
}process() 方法是模板方法,它定义了固定的步骤顺序。子类可以用不同的方式实现 loadData() 和 processData(),但顺序始终不变。saveResults() 方法是一个“钩子”,它有默认实现,子类可以选择重写。
当算法的步骤固定,但某些操作需要灵活调整,或者你想避免相似类之间的代码重复时,可以使用模板方法。
挑战
简单让我们使用模板方法模式构建一个报告生成器系统。你将创建一个框架,让不同类型的报告(例如销售报告和库存报告)遵循相同的生成流程,但每种报告类型都可以自定义特定步骤。这正是模板方法模式的理想应用场景:整体工作流程保持固定,而各个步骤可以变化。
你将把代码分成三个文件:
ReportGenerator.h:定义包含模板方法的抽象基类。创建一个
ReportGenerator类,用来定义报告生成算法的框架。你的模板方法generateReport()应按以下顺序执行这些步骤:gatherData():纯虚函数,必须由子类实现formatReport():纯虚函数,必须由子类实现addHeader():带有默认行为的钩子,会输出--- Report ---printReport():纯虚函数,必须由子类实现
addHeader()方法是一个钩子,子类可以选择重写它来自定义标题。Reports.h:实现两个具体的报告生成器。创建一个
SalesReport类:gatherData()输出Gathering sales data from databaseformatReport()输出Formatting sales figuresprintReport()输出Sales Total: $[amount],其中 amount 通过构造函数传入
创建一个
InventoryReport类:gatherData()输出Scanning inventory recordsformatReport()输出Organizing inventory by categoryaddHeader()重写钩子并输出=== Inventory Report ===printReport()输出Items in stock: [count],其中 count 通过构造函数传入
两个类都应通过各自的构造函数接收相应的值(amount 或 count)。
main.cpp:演示模板方法模式。读取两个输入:
- 销售金额(整数)
- 库存数量(整数)
使用销售金额创建一个
SalesReport并生成报告。然后使用库存数量创建一个InventoryReport并生成报告。为了便于阅读,在两份报告之间输出一个空行。
例如,输入 15000 和 250 时:
Gathering sales data from database
Formatting sales figures
--- Report ---
Sales Total: $15000
Scanning inventory records
Organizing inventory by category
=== Inventory Report ===
Items in stock: 250输入 8500 和 120 时:
Gathering sales data from database
Formatting sales figures
--- Report ---
Sales Total: $8500
Scanning inventory records
Organizing inventory by category
=== Inventory Report ===
Items in stock: 120注意,两份报告都遵循 generateReport() 中定义的完全相同的步骤顺序,但每份报告对这些步骤的实现各不相同。InventoryReport 还演示了如何重写钩子方法来自定义标题,而 SalesReport 使用默认行为。这就是模板方法模式:算法结构固定在基类中,而具体细节则保持灵活。
亲手试试
#include <iostream>
#include "Reports.h"
using namespace std;
int main() {
// 读取输入
int salesAmount;
int inventoryCount;
cin >> salesAmount;
cin >> inventoryCount;
// TODO:使用 salesAmount 创建 SalesReport 并生成报告
// TODO:在两份报告之间输出一个空行
// TODO:使用 inventoryCount 创建 InventoryReport 并生成报告
return 0;
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C++ 在线编译器