▶ code
В случаях, когда требуется эффективно, компактно, надежно реализовать обработку потока информации с потенциально большим количеством обработчиков, используется шаблон проектирования «Цепочка обязанностей».
Построить цепочку объектов для обработки вызова в последовательном порядке. Если один объект не может справиться с вызовом, он делегирует вызов следующему в цепи и так далее.
Шаблон «Цепочка обязанностей» позволяет:
избежать жесткой зависимости отправителя запроса от его получателя;
организовать обработку конкретного запроса несколькими объектами, связанными в цепочку;
вводить конвейер для запроса с множеством возможных обработчиков;
упростить структурные взаимосвязи между объектами. Отправитель запроса хранит ссылку на начало цепочки, а каждый
получатель имеет единственную ссылку на своего преемника последующий элемент в цепочке.
Использование:
Когда программа должна обрабатывать разнообразные запросы несколькими способами, но заранее неизвестно, какие конкретно запросы будут приходить и какие обработчики для них понадобятся.
Когда важно, чтобы обработчики выполнялись один за другим в строгом порядке.
Когда набор объектов, способных обработать запрос, должен задаваться динамически.
Преимущества:
Уменьшает зависимость между клиентом и обработчиками.
Реализует принцип единственной обязанности.
Реализует принцип открытости/закрытости.
Недостатки:
Связи с другими паттернами
Цепочка обязанностей, Команда, Посредник и Наблюдатель показывают различные способы работы отправителей запросов с их получателями:
Цепочка обязанностей передаёт запрос последовательно через цепочку потенциальных получателей, ожидая, что какой-то из них обработает запрос.
Команда устанавливает косвенную одностороннюю связь от отправителей к получателям.
Посредник убирает прямую связь между отправителями и получателями, заставляя их общаться опосредованно, через себя.
Наблюдатель передаёт запрос одновременно всем заинтересованным получателям, но позволяет им динамически подписываться или отписываться от таких оповещений.
Цепочку обязанностей часто используют вместе с Компоновщиком. В этом случае запрос передаётся от дочерних компонентов к их родителям.
Обработчики в Цепочке обязанностей могут быть выполнены в виде Команд. В этом случае множество разных операций может быть выполнено над одним и тем же контекстом, коим является запрос.
Но есть и другой подход, в котором сам запрос является Командой, посланной по цепочке объектов. В этом случае одна и та же операция может быть выполнена над множеством разных контекстов, представленных в виде цепочки.
Цепочка обязанностей и Декоратор имеют очень похожие структуры. Оба паттерна базируются на принципе рекурсивного выполнения операции через серию связанных объектов. Но есть и несколько важных отличий.
Обработчики в Цепочке обязанностей могут выполнять произвольные действия, независимые друг от друга, а также в любой момент прерывать дальнейшую передачу по цепочке. С другой стороны Декораторы расширяют какое-то определённое действие, не ломая интерфейс базовой операции и не прерывая выполнение остальных декораторов.
Chain.php
<?php
abstract class Handler
{
private ?Handler $successor;
public function __construct(?Handler $successor)
{
$this->successor = $successor;
}
/**
* Этот подход с использованием шаблонного метода pattern гарантирует вам, что
* каждый подкласс не забудет вызвать преемника
*/
final public function handle(TaskInterface $task): ?array
{
// запрос не был обработан этим обработчиком => смотрите следующий
$proceed = $this->processing($task);
if ($proceed === null && $this->successor) {
$proceed = $this->successor->handle($task);
}
return $proceed;
}
abstract public function processing(TaskInterface $task): ?array;
}
interface TaskInterface
{
public function getArray(): array;
}
class DevTask implements TaskInterface
{
private array $arr = [1, 2, 3,];
public function getArray(): array
{
return $this->arr;
}
}
class Senior extends Handler
{
public function processing(TaskInterface $task): ?array
{
return $task->getArray();
}
}
class Middle extends Handler
{
public function processing(TaskInterface $task): ?array
{
return null;
}
}
class Jun extends Handler
{
public function processing(TaskInterface $task): ?array
{
return null;
}
}
// 1
$task = new DevTask();
$senior = new Senior(null);
$middle = new Middle($senior);
$jun = new Jun($middle);
print_r($jun->handle($task)); // вернет массив
// 2
$task2 = new DevTask();
$jun = new Jun(null);
print_r($jun->handle($task2)); // НЕ вернет массив