Menu
Coddy logo textTech

Паттерн Strategy

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

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

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

<?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);
    }
}

Класс контекста принимает любую стратегию и делегирует вычисление:

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

challenge icon

Задание

Легко

Давайте создадим калькулятор стоимости доставки, используя паттерн «Стратегия» (Strategy Pattern). Различные методы доставки имеют разные правила ценообразования: стандартная доставка взимает фиксированную плату, экспресс-доставка добавляет процент, а ночная доставка сочетает в себе и то, и другое. Паттерн «Стратегия» позволяет вам менять эти расчеты, не изменяя код, который их использует.

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

  • ShippingStrategy.php — Создайте интерфейс ShippingStrategy, который определяет контракт для всех методов доставки. Он должен иметь метод calculate(float $weight): float, который принимает вес посылки и возвращает стоимость доставки.
  • ShippingMethods.php — Подключите файл ShippingStrategy и создайте три класса, реализующих этот интерфейс:
    • StandardShipping — Взимает фиксированную плату в размере $5.00 независимо от веса
    • ExpressShipping — Взимает $2.00 за килограмм (умножьте вес на 2)
    • OvernightShipping — Взимает базовую плату в размере $10.00 плюс $3.00 за килограмм
  • ShippingCalculator.php — Подключите файл ShippingStrategy и создайте класс ShippingCalculator, который выступает в роли контекста. Ваш калькулятор должен:
    • Принимать ShippingStrategy в своем конструкторе (используйте продвижение свойств в конструкторе — constructor promotion)
    • Иметь метод setStrategy(ShippingStrategy $strategy): void для изменения метода доставки во время выполнения программы
    • Иметь метод calculateCost(float $weight): float, который делегирует выполнение текущей стратегии
  • main.php — Подключите файлы ShippingMethods и ShippingCalculator. Вы получите два входных значения: тип доставки ("standard", "express" или "overnight") и вес посылки.

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

    Вывод должен быть в формате: Shipping cost: $[cost]

Эта задача демонстрирует, как паттерн «Стратегия» делает вашу систему доставки гибкой — добавление нового метода доставки, такого как SameDayShipping, требует только создания нового класса, реализующего интерфейс, без изменения калькулятора или существующих методов доставки.

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

<?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: Выведите результат, отформатированный до двух знаков после запятой
// Формат вывода: Shipping cost: $[cost]

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

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

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