Menu
Coddy logo textTech

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

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

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

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

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

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

challenge icon

Задание

Легко

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

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

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

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

    1. gatherData() — чисто виртуальный метод, должен быть реализован подклассами
    2. formatReport() — чисто виртуальный метод, должен быть реализован подклассами
    3. addHeader() — «хук» (hook) с поведением по умолчанию, который выводит --- 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 использует вариант по умолчанию. Это и есть паттерн «Шаблонный метод» в действии — структура алгоритма закреплена в базовом классе, но детали остаются гибкими.

Шпаргалка

Паттерн Шаблонный метод (Template Method) определяет основу алгоритма в базовом классе, позволяя подклассам переопределять определенные шаги, не изменяя общую структуру алгоритма.

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

#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";
    }
};

Шаблонный метод определяет фиксированную последовательность шагов. Подклассы реализуют абстрактные методы по-разному, но порядок никогда не меняется. Методы-хуки имеют реализации по умолчанию, которые подклассы могут при необходимости переопределить.

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

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

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

using namespace std;

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

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

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