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

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

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

Шпаргалка

Паттерн Стратегия (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]

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

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

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