Паттерн Шаблонный метод
Часть раздела Объектно-ориентированное программирование путешествия по 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) — он имеет реализацию по умолчанию, которую подклассы могут переопределить при необходимости.
Используйте Шаблонный метод, когда у вас есть алгоритм с неизменными шагами, но требуется гибкость в реализации конкретных операций, или когда вы хотите избежать дублирования кода в похожих классах.
Задание
ЛегкоДавайте создадим систему Генератора отчетов (Report Generator), используя паттерн «Шаблонный метод» (Template Method). Вы создадите структуру, в которой различные типы отчетов (например, отчеты о продажах и отчеты об инвентаризации) следуют одному и тому же процессу генерации, но каждый тип отчета настраивает определенные шаги под себя. Это идеальный сценарий для Шаблонного метода — общий рабочий процесс остается фиксированным, в то время как отдельные шаги различаются.
Вы организуете свой код в трех файлах:
ReportGenerator.h: Определите абстрактный базовый класс с шаблонным методом.Создайте класс
ReportGenerator, который определяет скелет алгоритма генерации отчета. Ваш шаблонный методgenerateReport()должен выполнять следующие шаги по порядку:gatherData()— чисто виртуальный метод, должен быть реализован подклассамиformatReport()— чисто виртуальный метод, должен быть реализован подклассамиaddHeader()— «хук» (hook) с поведением по умолчанию, который выводит--- 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 использует вариант по умолчанию. Это и есть паттерн «Шаблонный метод» в действии — структура алгоритма закреплена в базовом классе, но детали остаются гибкими.
Шпаргалка
Паттерн Шаблонный метод (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;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнер