Menu
Coddy logo textTech

Паттерн Strategy

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

Паттерн «Стратегия» — это поведенческий шаблон проектирования, который позволяет определить семейство алгоритмов, инкапсулировать каждый из них в собственном классе и делать их взаимозаменяемыми во время выполнения. Объекту, использующему стратегию, не нужно знать, какой именно алгоритм он выполняет.

Представьте корзину покупок, которая может применять различные стратегии скидок. Вместо того чтобы жёстко задавать логику скидок с помощью условных операторов, вы определяете каждую скидку как отдельный класс:

<?php
interface DiscountStrategy {
    public function calculate(float $total): float;
}

class NoDiscount implements DiscountStrategy {
    public function calculate(float $total): float {
        return $total;
    }
}

class PercentageDiscount implements DiscountStrategy {
    public function __construct(private int $percent) {}
    
    public function calculate(float $total): float {
        return $total - ($total * $this->percent / 100);
    }
}

class FixedDiscount implements DiscountStrategy {
    public function __construct(private float $amount) {}
    
    public function calculate(float $total): float {
        return max(0, $total - $this->amount);
    }
}

context class принимает любую стратегию и делегирует вычисление:

<?php
class ShoppingCart {
    public function __construct(private DiscountStrategy $discount) {}
    
    public function setDiscount(DiscountStrategy $discount): void {
        $this->discount = $discount;
    }
    
    public function checkout(float $total): float {
        return $this->discount->calculate($total);
    }
}

$cart = new ShoppingCart(new NoDiscount());
echo $cart->checkout(100) . "\n";

$cart->setDiscount(new PercentageDiscount(20));
echo $cart->checkout(100);

Результат:

100
80

Паттерн Strategy особенно полезен, когда у вас есть несколько способов выполнить операцию и вы хотите переключаться между ними, не изменяя class, который их использует. Добавление нового типа discount означает создание нового class, а не изменение существующего кода.

challenge icon

Задание

Легко

Давайте создадим калькулятор стоимости доставки с использованием шаблона Strategy. У разных способов доставки разные правила расчёта: стандартная доставка взимает фиксированную плату, экспресс-доставка добавляет процентную надбавку, а доставка на следующий день объединяет оба подхода. Шаблон Strategy позволяет менять эти расчёты, не изменяя код, который их использует.

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

  • ShippingStrategy.php: Create интерфейс ShippingStrategy, который define contract для всех способов доставки. В нём должен быть метод calculate(float $weight): float, принимающий вес посылки и возвращающий стоимость доставки.
  • ShippingMethods.php: подключите файл ShippingStrategy и создайте три class, которые реализуют интерфейс:
    • StandardShipping: взимает фиксированную плату $5.00 независимо от веса
    • ExpressShipping: взимает $2.00 за kilogram (умножьте вес на 2)
    • OvernightShipping: взимает базовую плату $10.00 плюс $3.00 за kilogram
  • ShippingCalculator.php: подключите файл ShippingStrategy и создайте class ShippingCalculator, который выступает в роли context. Ваш calculator должен:
    • Accept ShippingStrategy в своём constructor (используйте продвижение параметров constructor)
    • Have метод setStrategy(ShippingStrategy $strategy): void для изменения способа доставки во время выполнения
    • Have метод calculateCost(float $weight): float, который delegates текущей стратегии
  • main.php: подключите файлы ShippingMethods и ShippingCalculator. Вы получите два входных значения: тип доставки ("standard", "express" или "overnight") и вес посылки.

    Create ShippingCalculator, начиная с StandardShipping. В зависимости от входного типа доставки используйте setStrategy(), чтобы переключиться на appropriate способ доставки. Затем рассчитайте стоимость для given веса и выведите результат, отформатированный с точностью до двух знаков после десятичной точки.

    Результат должен иметь вид: Shipping cost: $[cost]

Этот challenge демонстрирует, как шаблон Strategy делает вашу систему доставки гибкой: добавление нового способа доставки, например SameDayShipping, требует только создания нового class, который реализует интерфейс, без изменения calculator или существующих способов доставки.

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

<?php
require_once 'ShippingMethods.php';
require_once 'ShippingCalculator.php';

// Прочитать ввод
$shippingType = trim(fgets(STDIN));
$weight = floatval(fgets(STDIN));

// TODO: Создать ShippingCalculator, начиная со StandardShipping

// TODO: На основе ввода типа доставки ("standard", "express" или "overnight"),
// использовать setStrategy() для переключения на соответствующий метод доставки

// TODO: Рассчитать стоимость для заданного веса

// TODO: Вывести результат, отформатированный до двух знаков после запятой
// Output format: Shipping cost: $[cost]

?>
quiz iconПроверьте себя

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

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

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