Паттерн 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, а не изменение существующего кода.
Задание
ЛегкоДавайте создадим калькулятор стоимости доставки с использованием шаблона 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 и создайте classShippingCalculator, который выступает в роли context. Ваш calculator должен:- Accept
ShippingStrategyв своём constructor (используйте продвижение параметров constructor) - Have метод
setStrategy(ShippingStrategy $strategy): voidдля изменения способа доставки во время выполнения - Have метод
calculateCost(float $weight): float, который delegates текущей стратегии
- Accept
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]
?>В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово $thisМетодыСвойстваКонструктор (__construct)Деструктор (__destruct)Повторение — простой калькулятор4Наследование
Основы наследованияКлючевое слово parent::Переопределение методовКлючевое слово finalАбстрактные классыПовторение — иерархия сотрудников7Инкапсуляция
Public, Protected, PrivateМодификаторы доступа подробноГеттеры и сеттерыСокрытие информацииПродвижение конструктора (8.0)Повторение — система учёта студентов2Пространства имён и автозагрузка
Введение в пространства имёнКлючевое слово useСтандарт автозагрузки PSR-4Автозагрузчик ComposerПовторение — структурированный проект5Интерфейсы и контракты
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовИнтерфейс и абстрактный классАннотация типов с интерфейсамиПовторение — калькулятор фигур8Магические методы
Введение в магические методы__toString и __debugInfo__get, __set, __isset, __unset__call и __callStatic__clone и клонирование объектов__serialize и __unserializeПовторение — пользовательская коллекция3Свойства классов
Свойства экземпляра и статические свойстваКонстанты в классахСтатические методы и свойстваПриватные и защищённые свойстваСвойства только для чтения (PHP 8.1)Повторение — менеджер банковского счёта6Полиморфизм
Повторение переопределения методовПолиморфизм через интерфейсыАннотации типов и объединённые типыПозднее статическое связываниеПовторение — обработчик платежей9Трейты
Введение в трейтыИспользование нескольких трейтовРазрешение конфликтов трейтовАбстрактные методы в трейтахТрейты и наследование12Паттерны проектирования, часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн StrategyПотренируйтесь самостоятельно: Онлайн-компилятор PHP