Menu
Coddy logo textTech

Паттерн Шаблонный метод

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 99 из 104.

Паттерн Template Method определяет скелет алгоритма в базовом классе, позволяя подклассам переопределять отдельные шаги без изменения общей структуры алгоритма. В отличие от Strategy, который заменяет алгоритмы целиком, Template Method сохраняет алгоритм неизменным, позволяя настраивать отдельные шаги.

Базовый класс реализует шаблонный метод (algorithm) и вызывает абстрактные или виртуальные методы, которые должны предоставить подклассы:

#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() является «хуком» — у него есть реализация по умолчанию, которую подклассы могут при необходимости переопределить.

Используйте Template Method, когда у вас есть алгоритм с неизменными шагами, но требуется гибкость в конкретных операциях, или когда вы хотите избежать дублирования кода в похожих классах.

challenge icon

Задание

Легко

Давайте создадим систему Report Generator с использованием паттерна Template Method. Вы создадите framework, в котором разные типы отчётов (например, отчёты о продажах и отчёты по запасам) следуют одному и тому же процессу формирования, но каждый тип отчёта настраивает определённые шаги. Это идеальный сценарий для Template Method: общий рабочий процесс остаётся фиксированным, а отдельные шаги различаются.

Вы организуете код в трёх файлах:

  • ReportGenerator.h: определите абстрактный базовый класс с шаблонным методом.

    Создайте класс ReportGenerator, который определяет каркас алгоритма формирования отчёта. Ваш шаблонный метод generateReport() должен выполнять следующие шаги по порядку:

    1. gatherData(): чисто виртуальный метод, который должен быть реализован производными классами
    2. formatReport(): чисто виртуальный метод, который должен быть реализован производными классами
    3. addHeader(): Hook с поведением по умолчанию, который выводит --- Report ---
    4. printReport(): чисто виртуальный метод, который должен быть реализован производными классами

    Метод addHeader() служит Hook, который производные классы могут при необходимости переопределить для настройки заголовка.

  • 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() переопределяет Hook, чтобы вывести === Inventory Report ===
    • printReport() выводит Items in stock: [count], где count передаётся конструктору

    Оба класса должны принимать соответствующие значения (amount или count) через свои конструкторы.

  • main.cpp: продемонстрируйте паттерн Template Method.

    Считайте два входных значения:

    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 также демонстрирует переопределение метода Hook для настройки заголовка, тогда как SalesReport использует поведение по умолчанию. Так работает паттерн Template Method: структура алгоритма зафиксирована в базовом классе, но детали остаются гибкими.

Попробуйте сами

#include <iostream>
#include "Reports.h"

using namespace std;

int main() {
    // Прочитать входные данные
    int salesAmount;
    int inventoryCount;
    cin >> salesAmount;
    cin >> inventoryCount;
    
    // TODO: Создать SalesReport с salesAmount и сгенерировать его
    
    // TODO: Вывести пустую строку между отчётами
    
    // TODO: Create an InventoryReport with inventoryCount and generate it
    
    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор C++