Паттерны проектирования в 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