НЕ МОЛЧИ!!!    Сделай что-нибудь, чтобы остановить войну России в Украине.
...бойтесь людей равнодушных - именно с их молчаливого согласия происходят все самые ужасные преступления на свете.   ("Репортаж с петлёй на шее")

Шаблон проектирования Chain of responsibility (Цепочка Обязанностей)

Рассматривается поведенческий шаблон проектирования Chain of responsibility (Цепочка Обязанностей), его описание и пример реализации.

Содержание

  • Применение
  • Реализация
  • Итоги

Применение

Современное программное обеспечение неуклонно усложняется. Как следствие, сейчас реакция на различные системные сообщения и пользовательские запросы зачастую требует весьма непростой обработки. Поведенческий шаблон проектирования Chain of responsibility призван облегчить сложную обработку за счет ее декомпозиции на ряд более простых операций, выполняемых последовательно одна за другой. Этим достигается ряд преимуществ:

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

Как видите — масса преимуществ. Теперь посмотрим, как это реализуется на практике.

Реализация

Преамбула

Достаточно наглядно использование шаблона можно показать на примере обработки http запроса. При его приеме серверу требуется решить множество различных задач. Это может быть проведение аутентификации и авторизации клиента, валидация запроса, взаимодействие с базой данных и бог знает что еще может понадобиться. Распутать весь этот клубок бизнес-логики и поможет шаблон Цепочка обязанностей. Для демонстрации создадим небольшое приложение, в котором пользователь будет отправлять запросы серверу и получать уведомления об обработке. Вот как оно будет выглядеть на клиенте, слева — форма запросов, справа — результат обработки запроса.

Описание примера

Основные моменты нашей реализации шаблона Chain of responsibility.

  • обработчики реализуют единый интерфейс обработки запроса;
  • для всех запросов существует единая точка входа;
  • элементарные обработчики связаны в цепочку обработчиков;
  • каждый обработчик хранит единственную ссылку на следующий обработчик;
  • каждый обработчик либо производит обработку, либо передает запрос следующему, возможна частичная обработка запроса одним или несколькими обработчиками;
  • обработчик сам решает заканчивать обработку в цепочке или нет.

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

/**
 * Интерфейс, реализуемый каждым обработчиком в цепочке
 */
interface Handler
{
    public function handle(string $request);
    public function setNext(Handler $handler);
}

Часто есть смысл создать базовый класс обработчика, в который, во избежание дублирования в наследниках, помещают общий код обработчиков, например, валидацию входных данных, ссылку на следующий обработчик. Обратите внимание, метод setNext(), кроме прочего, возвращает ссылку на следующий обработчик. Этим достигается упрощение построения цепочки обработчиков: $handler1->setNext($handler2)->setNext($handler3).

/**
 * Класс базовой обработки запроса
 */
abstract class BaseHandler implements Handler
{
    /**
     * ссылка на следующий обработчик
     */
    private $next;

    public function handle(string $request)
    {
        $res = 'Запроc <strong>' . $request .'</strong> не был обработан';
        return $res; 
    }

    public function setNext(Handler $handler)
    {
        $this->next = $handler;
        return $handler;
    }

    public function getNext()
    {
        return $this->next;
    }
}

Далее нам надо реализовать классы конкретных обработчиков запроса.


/**
 * Первый обработчик в цепочке обязанностей
 */
Class Handler1 extends BaseHandler
{
    public function handle(string $request)
    {
        if ($request === "request1") {
            // обработка запроса 
            $res = 'Запрос <strong>' . $request . '</strong> успешно обработан';
        } else {
            if ($this->getNext()) {
                $res = $this->getNext()->handle($request);
            } else {
               $res = parent::handle($request)
            }
        }
    }
}

Таким же образом строятся классы других обработчиков цепочки, Handler2, Handler3.

Теперь создадим клиентcкий index.html, отправляющий на сервер запросы. Файл стилей я приводить не буду, поскольку в конце будет дана ссылка на архив с приложением.

<!DOCTYPE html>
<html>
    <head>
        <title>Chain of responsibility</title>
        <link rel="icon" href="favicon.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    
    <body>
        <div class="wrap">
            <h3 class="title">Шаблон проектирования 
                <br>
                <span>Chain of responsibility</span>
            </h3>
            <div class="container">
                <fieldset>
                <legend>Отправьте запрос</legend>
                <form action="getResponse.php" method="GET">
                <p>
                <select id="carId" name="request">
                    <option value="request01">Запрос №01</option>
                    <option value="request02">Запрос №02</option>
                    <option value="request03">Запрос №03</option>
                    <option value="request04">Запрос №04</option>
                    <option value="request05">Запрос №05</option>
                </select>
                </p>    
                <p>
                    <input type="submit" value="Отправить">
                </p>
                </form>
                </fieldset>
            </div>    
        </div>
    </body>
</html>

Ниже следует код простенького getResponse.php, принимающего запросы.

<?php

include 'Handler.php';
include 'BaseHandler.php';
include 'Handler1.php';
include 'Handler2.php';
include 'Handler3.php';


$req = htmlspecialchars($_GET["request"]);

$handler1 = new Handler1();
$handler2 = new Handler2();
$handler3 = new Handler3();
$handler1->setNext($handler2);
$handler2->setNext($handler3);

$resp = $handler1->handle($req);
$res = include 'Response.php';
echo $res;

Обратите внимание, клиент может отправить 5 запросов, а сервер обрабатывает только 3. Это сделано сознательно, чтобы показать возможное поведение сервера, если обработчик запроса в цепочке не будет найден.
Замечу, если не стоит задача динамического построения цепочки обработчиков, то код существенно упрощается. Во-первых, при из обработчиков и интерфейса Handler, убираются методы setNext() и getNext(), а ссылка на следующий обработчик передается параметром в конструктор базового обработчика BaseHandler. Во-вторых, отпадает необходимость при каждом запросе создавать цепочку обработчиков, которая строится в этом случае один раз.

По приведенной ссылке желающие могут скачать архив с приведенным примером.

Итоги

Итак, сегодня мы рассмотрели поведенческий шаблон проектирования chain of responsibility или цепочка обязанностей. Еще раз подчеркну, использование шаблона позволяет упростить разработку и поддержку приложений за счет разбиения кода обработки на последовательность более простых. Это также позволяет легко наращивать обработчик новыми. Кроме того, шаблон позволяет не только убрать жесткую зависимость клиентского запроса от обработчика, но и динамически настраивать обработку в зависимости от внешних условий.