Menu
Coddy logo textTech

Паттерн 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, когда у вас есть несколько алгоритмов для конкретной задачи и вы хотите динамически переключаться между ними или когда хотите избежать условных операторов для выбора поведения.

challenge icon

Задание

Легко

Давайте создадим Shipping Calculator, который использует паттерн Strategy для расчёта стоимости доставки на основе разных способов доставки. Это практический сценарий, в котором нужно менять алгоритмы во время выполнения: одна и та же посылка может доставляться наземным, воздушным или экспресс-способом, и у каждого есть своя логика расчёта цены.

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

  • ShippingStrategy.h: Define интерфейс стратегии и конкретные стратегии доставки.

    Создайте абстрактный ShippingStrategy class с 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, который использует стратегию доставки.

    Ваш ShippingService class должен хранить std::unique_ptr<ShippingStrategy> как private member. Implement:

    • Метод setStrategy(std::unique_ptr<ShippingStrategy> strategy) для изменения способа доставки
    • Метод calculateShipping(double weight), который использует current стратегию для вычисления и возврата стоимости

    Если при вызове calculateShipping стратегия не установлена, верните 0.0.

  • main.cpp: Продемонстрируйте переключение стратегий во время выполнения.

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

    1. Вес посылки (double)
    2. Способ доставки: 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;
}
quiz iconПроверьте себя

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

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

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