Паттерны проектирования в PHP: продвинутые техники с примерами кода

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Паттерны проектирования в PHP: продвинутые техники с примерами кода

Паттерны проектирования — это проверенные решения типовых задач, возникающих при разработке программного обеспечения. Если вы уже освоили базовые паттерны (Singleton, Factory, Observer), пришло время перейти на следующий уровень. В этой статье мы разберем четыре продвинутых поведенческих паттерна на PHP: Стратегия, Команда, Состояние и Цепочка обязанностей. Вы узнаете, как они помогают писать гибкий, поддерживаемый и масштабируемый код.

1. Паттерн Стратегия (Strategy)

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

Когда использовать:

  • У вас есть несколько вариантов алгоритмов, которые могут меняться во время выполнения.
  • Вы хотите избежать громоздких условных конструкций (if-else или switch).
  • Алгоритмы могут быть переиспользованы в разных частях приложения.

Пример: система расчета стоимости доставки

Представьте, что у вас интернет-магазин, и стоимость доставки зависит от выбранного перевозчика: Почта России, DHL или курьерская служба.

<?php

// Интерфейс стратегии
interface ShippingStrategy {
    public function calculate(float $weight, float $distance): float;
}

// Конкретные стратегии
class RussianPostShipping implements ShippingStrategy {
    public function calculate(float $weight, float $distance): float {
        return ($weight * 0.5) + ($distance * 0.1);
    }
}

class DHLShipping implements ShippingStrategy {
    public function calculate(float $weight, float $distance): float {
        return ($weight * 1.2) + ($distance * 0.3) + 10;
    }
}

class CourierShipping implements ShippingStrategy {
    public function calculate(float $weight, float $distance): float {
        return ($weight * 0.8) + ($distance * 0.2) + 5;
    }
}

// Контекст
class Order {
    private float $weight;
    private float $distance;
    private ShippingStrategy $strategy;

    public function __construct(float $weight, float $distance, ShippingStrategy $strategy) {
        $this->weight = $weight;
        $this->distance = $distance;
        $this->strategy = $strategy;
    }

    public function setStrategy(ShippingStrategy $strategy): void {
        $this->strategy = $strategy;
    }

    public function getShippingCost(): float {
        return $this->strategy->calculate($this->weight, $this->distance);
    }
}

// Использование
$order = new Order(2.5, 100, new RussianPostShipping());
echo "Доставка Почтой России: " . $order->getShippingCost() . " руб.\n";

$order->setStrategy(new DHLShipping());
echo "Доставка DHL: " . $order->getShippingCost() . " руб.\n";
?>

В этом примере мы легко меняем алгоритм расчета стоимости, не трогая класс Order. Добавление нового перевозчика сводится к созданию нового класса, реализующего интерфейс ShippingStrategy.

2. Паттерн Команда (Command)

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

Когда использовать:

  • Нужно реализовать отмену (undo) и повтор (redo) операций.
  • Требуется ставить операции в очередь или выполнять их асинхронно.
  • Вы хотите отделить отправителя запроса от его получателя.

Пример: текстовый редактор с отменой действий

<?php

// Интерфейс команды
interface Command {
    public function execute(): void;
    public function undo(): void;
}

// Получатель (Receiver)
class TextEditor {
    private string $text = '';

    public function insert(string $text): void {
        $this->text .= $text;
    }

    public function delete(int $length): void {
        $this->text = substr($this->text, 0, -$length);
    }

    public function getText(): string {
        return $this->text;
    }
}

// Конкретная команда: вставка текста
class InsertCommand implements Command {
    private TextEditor $editor;
    private string $text;

    public function __construct(TextEditor $editor, string $text) {
        $this->editor = $editor;
        $this->text = $text;
    }

    public function execute(): void {
        $this->editor->insert($this->text);
    }

    public function undo(): void {
        $this->editor->delete(strlen($this->text));
    }
}

// Конкретная команда: удаление текста
class DeleteCommand implements Command {
    private TextEditor $editor;
    private int $length;
    private string $deletedText = '';

    public function __construct(TextEditor $editor, int $length) {
        $this->editor = $editor;
        $this->length = $length;
    }

    public function execute(): void {
        $currentText = $this->editor->getText();
        $this->deletedText = substr($currentText, -$this->length);
        $this->editor->delete($this->length);
    }

    public function undo(): void {
        $this->editor->insert($this->deletedText);
    }
}

// Invoker (отправитель)
class CommandHistory {
    private array $history = [];

    public function executeCommand(Command $command): void {
        $command->execute();
        $this->history[] = $command;
    }

    public function undo(): void {
        $command = array_pop($this->history);
        if ($command) {
            $command->undo();
        }
    }
}

// Использование
$editor = new TextEditor();
$history = new CommandHistory();

$cmd1 = new InsertCommand($editor, "Привет, ");
$history->executeCommand($cmd1);

$cmd2 = new InsertCommand($editor, "мир!");
$history->executeCommand($cmd2);

echo $editor->getText() . "\n"; // Привет, мир!

$history->undo();
echo $editor->getText() . "\n"; // Привет,

$cmd3 = new DeleteCommand($editor, 3);
$history->executeCommand($cmd3);
echo $editor->getText() . "\n"; // При

$history->undo                

Блоги

Рекомендаций по книгам