Паттерн 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). Различные методы доставки имеют разные правила ценообразования: стандартная доставка взимает фиксированную плату, экспресс-доставка добавляет процент, а ночная доставка сочетает в себе и то, и другое. Паттерн Стратегия позволяет вам переключать эти расчеты, не меняя код, который их использует.
Вы организуете свой код в четырех файлах:
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, требует только создания нового класса, реализующего интерфейс, без изменения калькулятора или существующих методов доставки.
Шпаргалка
Паттерн Стратегия (Strategy Pattern) — это поведенческий паттерн проектирования, который определяет семейство алгоритмов, инкапсулирует каждый из них в отдельный класс и делает их взаимозаменяемыми во время выполнения программы.
Определите интерфейс для стратегии:
<?php
interface DiscountStrategy {
public function calculate(float $total): float;
}Реализуйте конкретные стратегии:
<?php
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);
}
}Используйте паттерн, внедряя стратегии:
<?php
$cart = new ShoppingCart(new NoDiscount());
echo $cart->checkout(100); // 100
$cart->setDiscount(new PercentageDiscount(20));
echo $cart->checkout(100); // 80Паттерн Стратегия позволяет переключаться между алгоритмами без изменения класса, который их использует. Добавление новых стратегий требует только создания новых классов, реализующих интерфейс.
Попробуйте сами
<?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]
?>В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово $thisМетодыСвойстваКонструктор (__construct)Деструктор (__destruct)Итоги — Простой калькулятор4Наследование
Основы наследованияКлючевое слово parent::Переопределение методовКлючевое слово finalАбстрактные классыИтоги — Иерархия сотрудников7Инкапсуляция
Public, Protected, PrivateПодробный разбор модификаторов доступаГеттеры и сеттерыСокрытие данныхConstructor Promotion (8.0)Итоги — Система учета студентов10Продвинутые концепции ООП
Композиция против наследованияВнедрение зависимостейАнонимные классыПеречисления (PHP 8.1)Файберы (PHP 8.1)Глубокое погружение в клонирование объектовГенераторы и итераторы13Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий2Пространства имен и автозагрузка
Введение в пространства именКлючевое слово useСтандарт автозагрузки PSR-4Автозагрузчик ComposerИтоги: структурированный проект5Интерфейсы и контракты
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовИнтерфейс против абстрактного классаType Hinting с интерфейсамиИтоги — Калькулятор фигур8Магические методы
Введение в магические методы__toString и __debugInfo__get, __set, __isset, __unset__call и __callStatic__clone и клонирование объектов__serialize и __unserializeИтоги — Кастомная коллекция11Система типов и обработка ошибок
Объявление типовNullable-типыТипы объединения и пересеченияКлассы исключенийИерархия пользовательских исключенийTry, Catch, FinallyИтоги — Валидатор форм14Проект: Управление библиотекой
Обзор проектаКлассы Book и User3Свойства классов
Свойства экземпляра и статические свойстваКонстанты в классахСтатические методы и свойстваПриватные и защищенные свойстваСвойства Readonly (PHP 8.1)Итоги — Менеджер банковского счета6Полиморфизм
Снова о переопределении методовПолиморфизм через интерфейсыType Hinting и Union TypesПозднее статическое связываниеИтоги — Payment Processor9Трейты
Введение в трейтыИспользование нескольких трейтовРазрешение конфликтов трейтовАбстрактные методы в трейтахТрейты и наследование12Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy