Chain of Responsibility (Цепочка обязанностей)¶
Категория: паттерн поведения.
Проблема¶
Запрос может быть обработан одним из нескольких обработчиков, но заранее неизвестно, каким именно - это зависит от свойств самого запроса (уровень серьёзности заявки в поддержку, тип исключения, права доступа). Если весь выбор обработчика зашить в один метод с большим if/switch, этот метод придётся менять при добавлении каждого нового вида обработки, а сам список обработчиков будет сложно переиспользовать или переупорядочить.
Решение¶
- Обработчики выстраиваются в цепочку: каждый хранит ссылку на следующий обработчик.
- Получив запрос, обработчик решает: может ли он сам его обработать. Если да - обрабатывает (и обычно на этом цепочка останавливается). Если нет - передаёт запрос дальше по цепочке.
- Отправитель запроса обращается только к первому обработчику в цепочке и не знает, сколько их всего и кто из них в итоге обработает запрос.
- Цепочку можно собирать и переупорядочивать динамически, не меняя код самих обработчиков.
Структура¶
Handler- интерфейс/базовый класс со ссылкой на следующий обработчик и методом обработки запроса.ConcreteHandler- решает, может ли обработать запрос; если нет - передаёт его следующему звену.Client- формирует цепочку и отправляет запрос первому обработчику.
Когда применять¶
- Больше одного объекта может обработать запрос, и обработчик должен определяться динамически по свойствам запроса, а не жёстко назначаться заранее.
- Нужно отправить запрос одному из нескольких обработчиков, явно не указывая получателя.
- Набор обработчиков и их порядок должны настраиваться независимо от кода, который инициирует запрос (например, конфигурируемый порядок middleware, валидаторов, обработчиков исключений).
Плюсы¶
- Уменьшает связанность между отправителем запроса и конкретными обработчиками - отправитель знает только про первое звено цепочки.
- Позволяет добавлять, убирать и переупорядочивать обработчики без изменения существующего кода (открытость/закрытость).
- Каждый обработчик отвечает за одно узкое условие - удобно тестировать по отдельности.
Минусы¶
- Нет гарантии, что запрос вообще будет обработан - если ни один обработчик не подошёл и не предусмотрен "обработчик по умолчанию" в конце цепочки, запрос молча "теряется".
- Длинная цепочка усложняет отладку - чтобы понять, кто и почему обработал (или не обработал) конкретный запрос, приходится прослеживать вызовы по всей цепочке.
- Порядок обработчиков в цепочке имеет значение, и его легко случайно нарушить при добавлении нового звена.
Отличие от Decorator¶
Структурно оба паттерна похожи - каждое звено хранит ссылку на следующее и решает, что делать с вызовом. Но назначение разное: Chain of Responsibility передаёт запрос дальше только если текущий обработчик не может (или не должен) обработать его сам, и как правило только один обработчик в итоге реально что-то делает. Decorator всегда вызывает следующий объект в цепочке и лишь добавляет к его результату своё поведение - выполняются все звенья цепочки, а не одно из них.
Пример в .NET Framework / BCL¶
- ASP.NET Core middleware pipeline - каждый middleware решает, обработать ли запрос самому (вернуть ответ) или передать его дальше по конвейеру через
next(). - Обработка исключений через вложенные
try/catchразных уровней приложения - концептуально похожая идея передачи "необработанного" случая на следующий уровень. - Валидация в виде цепочки правил, где каждое правило проверяет свой аспект и передаёт объект дальше, если само не нашло нарушений.
Пример реализации на C#¶
Открыть ChainOfResponsibility.cs отдельно
Скачать ChainOfResponsibility.cs