Menu
CoddyTech

模板方法模式

本课属于 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() 方法是一个“钩子”,它有默认实现,子类可以选择重写。

当算法的步骤固定,但某些操作需要灵活调整,或者你想避免相似类之间的代码重复时,可以使用模板方法。

challenge icon

挑战

简单

让我们使用模板方法模式构建一个报告生成器系统。你将创建一个框架,让不同类型的报告(例如销售报告和库存报告)遵循相同的生成流程,但每种报告类型都可以自定义特定步骤。这正是模板方法模式的理想应用场景:整体工作流程保持固定,而各个步骤可以变化。

你将把代码分成三个文件:

  • ReportGenerator.h:定义包含模板方法的抽象基类。

    创建一个 ReportGenerator 类,用来定义报告生成算法的框架。你的模板方法 generateReport() 应按以下顺序执行这些步骤:

    1. gatherData():纯虚函数,必须由子类实现
    2. formatReport():纯虚函数,必须由子类实现
    3. addHeader():带有默认行为的钩子,会输出 --- Report ---
    4. printReport():纯虚函数,必须由子类实现

    addHeader() 方法是一个钩子,子类可以选择重写它来自定义标题。

  • Reports.h:实现两个具体的报告生成器。

    创建一个 SalesReport 类:

    • gatherData() 输出 Gathering sales data from database
    • formatReport() 输出 Formatting sales figures
    • printReport() 输出 Sales Total: $[amount],其中 amount 通过构造函数传入

    创建一个 InventoryReport 类:

    • gatherData() 输出 Scanning inventory records
    • formatReport() 输出 Organizing inventory by category
    • addHeader() 重写钩子并输出 === Inventory Report ===
    • printReport() 输出 Items in stock: [count],其中 count 通过构造函数传入

    两个类都应通过各自的构造函数接收相应的值(amount 或 count)。

  • main.cpp:演示模板方法模式。

    读取两个输入:

    1. 销售金额(整数)
    2. 库存数量(整数)

    使用销售金额创建一个 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;
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C++ 在线编译器