Паттерн Strategy
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 95 из 104.
Шаблон Strategy определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми. Это позволяет изменять поведение объекта во время выполнения без изменения его кода — алгоритм изменяется независимо от клиентов, которые его используют.
Шаблон состоит из трёх частей: интерфейса Strategy, объявляющего метод алгоритма, конкретных стратегий, реализующих различные варианты, и Context, использующего стратегию:
#include <iostream>
#include <memory>
// Интерфейс стратегии
class PaymentStrategy {
public:
virtual void pay(int amount) = 0;
virtual ~PaymentStrategy() = default;
};
// Конкретные стратегии
class CreditCardPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " via Credit Card\n";
}
};
class PayPalPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " via PayPal\n";
}
};
// Контекст
class ShoppingCart {
std::unique_ptr<PaymentStrategy> strategy;
public:
void setPaymentMethod(std::unique_ptr<PaymentStrategy> s) {
strategy = std::move(s);
}
void checkout(int total) {
if (strategy) strategy->pay(total);
}
};
int main() {
ShoppingCart cart;
cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
cart.checkout(100);
cart.setPaymentMethod(std::make_unique<PayPalPayment>());
cart.checkout(50);
}ShoppingCart не знает, какой способ оплаты он использует, — он просто вызывает pay() у установленной стратегии. Ты можешь менять стратегии во время выполнения с помощью setPaymentMethod(), благодаря чему система остаётся гибкой и её легко расширять, добавляя новые варианты оплаты.
Используйте Strategy, когда у вас есть несколько алгоритмов для конкретной задачи и вы хотите динамически переключаться между ними или когда хотите избежать условных операторов для выбора поведения.
Задание
ЛегкоДавайте создадим Shipping Calculator, который использует паттерн Strategy для расчёта стоимости доставки на основе разных способов доставки. Это практический сценарий, в котором нужно менять алгоритмы во время выполнения: одна и та же посылка может доставляться наземным, воздушным или экспресс-способом, и у каждого есть своя логика расчёта цены.
Вы организуете код в трёх файлах:
ShippingStrategy.h: Define интерфейс стратегии и конкретные стратегии доставки.Создайте абстрактный
ShippingStrategyclass с pure virtual методомcalculateCost(double weight), который возвращает стоимость доставки как значение типа double, а также с виртуальным деструктором.Затем Implement три конкретные стратегии:
GroundShipping: стоимость составляет1.5за единицу веса (weight * 1.5)AirShipping: стоимость составляет4.0за единицу веса (weight * 4.0)ExpressShipping: стоимость составляет6.5за единицу веса плюс фиксированная плата10.0(weight * 6.5 + 10.0)
ShippingService.h: Создайте Context class, который использует стратегию доставки.Ваш
ShippingServiceclass должен хранитьstd::unique_ptr<ShippingStrategy>как private member. Implement:- Метод
setStrategy(std::unique_ptr<ShippingStrategy> strategy)для изменения способа доставки - Метод
calculateShipping(double weight), который использует current стратегию для вычисления и возврата стоимости
Если при вызове
calculateShippingстратегия не установлена, верните0.0.- Метод
main.cpp: Продемонстрируйте переключение стратегий во время выполнения.Считайте два входных значения:
- Вес посылки (double)
- Способ доставки:
ground,airилиexpress
Создайте
ShippingServiceи установите appropriate стратегию на основе введённого способа доставки. Вычислите и выведите стоимость доставки.Затем переключитесь на другую стратегию (используйте
air, если введено неair, в противном случае используйтеground) и снова вычислите стоимость для того же веса. Это демонстрирует преимущества замены стратегий во время выполнения.Выводите каждую стоимость в отдельной строке ровно с одним десятичным знаком, добавляя перед ней название способа:
[Method]: $[cost]
Например, для входных значений 5.0 и ground:
Ground: $7.5
Air: $20.0Для входных значений 3.0 и express:
Express: $29.5
Air: $12.0Для входных значений 10.0 и air:
Air: $40.0
Ground: $15.0Обратите внимание, что ShippingService не нужно знать детали каждого алгоритма расчёта цены. Он просто делегирует вычисление стратегии, которая установлена в current момент. Вы можете легко добавлять новые способы доставки (например, доставку дронами или доставку в тот же день), не изменяя service class.
Попробуйте сами
#include <iostream>
#include <string>
#include <iomanip>
#include <memory>
#include "ShippingStrategy.h"
#include "ShippingService.h"
int main() {
double weight;
std::string method;
std::cin >> weight;
std::cin >> method;
// Установить вывод с 1 десятичным знаком
std::cout << std::fixed << std::setprecision(1);
ShippingService service;
// TODO: На основе входного метода ("ground", "air" или "express"):
// 1. Установить соответствующую стратегию для сервиса
// 2. Вычислить и вывести стоимость в формате: "[Method]: $[cost]"
// TODO: Переключиться на другую стратегию:
// - Если ввод был "air", переключиться на GroundShipping
// - Иначе переключиться на AirShipping
// Вычислить и вывести новую стоимость
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Итоги — Обобщенный контейнерПотренируйтесь самостоятельно: Онлайн-компилятор C++