Реализация паттерна Chain of Responsibility на примере котиков в PHP
Привет, Хабр!
Если вы когда‑нибудь пытались настроить бизнес‑логику в своём проекте так, чтобы она не выглядела как свалка if-else
и работала хорошо, то этот материал для вас. Сегодня мы разберём один из самых приятных паттернов — Chain of Responsibility, или «Цепочка обязанностей».
Вместо кучи условий, которые как минимум трудно читать, а как максимум невозможно поддерживать, мы строим гибкую архитектуру. Каждый этап обработки запроса становится отдельным модулем. А благодаря возможности менять порядок этих модулей или добавлять новые, система легко масштабируется.
Используйте Chain of Responsibility, когда:
Логика обработки запроса должна быть модульной.
Нужно динамически менять последовательность обработки.
Вы хотите облегчить добавление новых обработчиков.
Сразу перейдем к коду
Реализация паттерна на примере магазина котиков
Архитектура магазина котиков
Вот как будет выглядеть наш процесс:
Проверка наличия товара.
Проверка возраста покупателя.
Проверка оплаты.
Упаковка заказа.
Каждое из этих действий — это отдельный обработчик в цепочке.
Интерфейс обработчика
Начнём с базового интерфейса, который будут реализовывать наши обработчики.
nextHandler = $handler;
return $handler;
}
public function handle(array $request): ?array
{
if ($this->nextHandler) {
return $this->nextHandler->handle($request);
}
return $request;
}
}
Интерфейс HandlerInterface
определяет контракт для всех обработчиков, а базовый класс AbstractHandler
реализует передачу запроса следующему обработчику.
Обработчики
Теперь создадим обработчики для проверки заказа. Начнем с проверки наличия заказа:
Теперь реализуем проверку возраста покупателя:
Проверка оплаты:
Упаковка и подготовка к доставке:
Сборка цепочки
Теперь объединим все обработчики в цепочку.
5,
'age' => 25,
'payment' => true,
'payment_id' => 'PAY12345',
];
$stockHandler = new StockHandler();
$ageHandler = new AgeVerificationHandler();
$paymentHandler = new PaymentHandler();
$packagingHandler = new PackagingHandler();
$stockHandler->setNext($ageHandler)
->setNext($paymentHandler)
->setNext($packagingHandler);
try {
$result = $stockHandler->handle($request);
echo "Заказ успешно обработан: " . json_encode($result, JSON_PRETTY_PRINT);
} catch (RuntimeException $e) {
error_log("Ошибка обработки заказа: " . $e->getMessage());
echo "Ошибка: " . $e->getMessage();
}
Если что‑то идёт не так, выбрасываем RuntimeException
, а все важные этапы логируются через error_log
(или можно заменить на тот же Monolog).
Не забываем покрыть код тестами:
expectException(RuntimeException::class);
$this->expectExceptionMessage('Товара нет в наличии.');
$handler->handle(['stock' => 0]);
}
public function testChainProcessesRequestSuccessfully()
{
$request = [
'stock' => 5,
'age' => 25,
'payment' => true,
'payment_id' => 'PAY12345',
];
$stockHandler = new StockHandler();
$ageHandler = new AgeVerificationHandler();
$paymentHandler = new PaymentHandler();
$packagingHandler = new PackagingHandler();
$stockHandler->setNext($ageHandler)
->setNext($paymentHandler)
->setNext($packagingHandler);
$result = $stockHandler->handle($request);
$this->assertEquals('ready_for_delivery', $result['status']);
}
}
Что ещё можно улучшить?
Динамическая конфигурация цепочки.
Например, настраивать последовательность обработчиков через тот же JSON или YAML.Производительность.
Для больших цепочек можно добавить кэширование результатов, чтобы не проходить одни и те же проверки повторно.Логирование.
Подключаем Monolog для более подробного логирования.
Как вы заметили, паттерн упрощает сложные процессы, разбивая их на независимые шаги. А какое применение паттерну находили вы? Делитесь в комментариях!
Всем PHP-разработчикам рекомендую посетить открытый урок «Вебсокеты на PHP, или как написать свой чат», который пройдет 22 января в Otus. Записаться можно по ссылке.