Обзор шаблона проектирования цепочки ответственности и его реализации в Dart и Flutter

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

Оглавление

  • Что такое шаблон проектирования «Цепочка ответственности»?
  • Анализ
  • Реализация
  • Другие статьи из этой серии
  • Ваш вклад

Что такое шаблон проектирования «Цепочка ответственности»?

Цепочка ответственности (CoR), также известная как Цепочка команд, представляет собой шаблон поведенческого проектирования, намерение которого в книге GoF описывается следующим образом:

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

TL; DR: шаблон проектирования цепочки ответственности - это упорядоченный список обработчиков сообщений, которые знают, как делать две вещи: обрабатывать определенный тип сообщения или передавать сообщение следующему обработчику сообщений.

Прежде всего, шаблон проектирования Цепочка ответственности является поведенческим, что означает, что его основная цель - переработать основной рабочий процесс (поведение) и разделить его на несколько отдельных частей или автономных объектов (вспомните шаблоны проектирования Команда или Состояние. в качестве примеров). Допустим, у вас есть какой-то рабочий процесс, определенный в вашем коде, где каждый шаг должен выполняться последовательно. Работает и все нормально, пока…

  • Следует ввести некоторые дополнительные шаги. Хорошо, ничего страшного, просто добавьте их.
  • Некоторые из этих шагов не являются обязательными в зависимости от запроса. Что ж, давайте добавим условные блоки, ничего особенного.
  • К сожалению, мы забыли о проверке ... Хм, код как-то раздувается.
  • Появляется необъятный запрос функции: порядок шагов зависит от запроса. Пожалуйста, погляди…

Что ж, я надеюсь, вы понимаете, что этот код может легко превратиться в беспорядок (не говоря уже о нарушении принципа открытого-закрытого - буква O в принципах SOLID). Паттерн CoR предлагает разделить каждый шаг на отдельный компонент - обработчик - а затем связать эти обработчики в цепочку. Каждый обработчик содержит ссылку на следующий, поэтому после получения запроса он обрабатывается обработчиком и передается следующему по цепочке до тех пор, пока рабочий процесс не будет завершен. В результате у нас все еще есть такое же последовательное выполнение кода, но теперь каждый шаг разделен, дополнительные шаги могут быть добавлены без изменения существующего кода. Но подождите, это еще не все!

Шаблон «Цепочка ответственности» позволяет переупорядочивать, добавлять или удалять обработчики в цепочке во время выполнения - насколько это круто, правда? Кроме того, каждый обработчик может быть реализован таким образом, чтобы он мог решать, передавать ли запрос дальше по цепочке или нет.

Здесь уже упоминалось множество замечательных идей, так что давайте сразу перейдем к более подробному анализу шаблона проектирования CoR и его реализации!

Анализ

Общая структура шаблона проектирования цепочки ответственности выглядит так:

  • Обработчик - определяет интерфейс для обработки запросов. Этот интерфейс является необязательным, если все обработчики расширяют класс BaseHandler - тогда достаточно одного абстрактного метода для обработки запросов;
  • BaseHandler - абстрактный класс, который содержит шаблонный код, общий для всех классов обработчиков, и поддерживает ссылку на следующий объект обработчика в цепочке. Кроме того, класс может реализовать поведение обработки по умолчанию, например. он может передать запрос следующему обработчику, если он есть;
  • ConcreteHandlers - содержат фактический код для обработки запроса. Получив запрос, обработчик мог либо обработать его, либо передать по цепочке. Обычно обработчики неизменяемы после инициализации ;
  • Клиент - составляет цепочку (и) обработчиков, а затем инициирует запрос к объекту ConcreteHandler в цепочке.

Применимость

Шаблон проектирования «Цепочка ответственности» следует использовать, когда ожидается, что система будет обрабатывать различные типы запросов различными способами, но ни типы запросов, ни последовательность обработки не определены во время компиляции. Шаблон позволяет связать несколько обработчиков в одну цепочку и позволяет клиенту передавать запросы по этой цепочке. В результате каждый обработчик получит запрос и обработает его и / или передаст дальше. Кроме того, чтобы решить проблему с неизвестной последовательностью обработки, обработчики могут предоставить установщики для ссылочного поля преемника внутри классов обработчиков - вы сможете добавлять, удалять или переупорядочивать обработчики во время выполнения, тем самым изменяя последовательность обработки запроса. .

Кроме того, шаблон CoR следует использовать, когда один запрос должен обрабатываться несколькими обработчиками, обычно в определенном порядке. В этом случае цепочка может быть определена во время компиляции, и все запросы будут проходить через цепочку точно так, как планировалось. Если порядок выполнения не имеет значения, просто бросьте кости и постройте цепочку в случайном порядке - все обработчики все равно получат запрос и обработают его.

Наконец, нужно помнить об одном: получение не гарантируется. Поскольку CoR вводит слабую связь между отправителем и получателем, и запрос может обрабатываться любым обработчиком в цепочке, нет никакой гарантии, что он действительно будет обработан. В случаях, когда запрос должен обрабатываться хотя бы одним обработчиком, вы должны убедиться, что цепочка настроена правильно, например, добавив какой-то обработчик монитора в конце цепочки, который уведомляет о необработанных запросах и / или выполняет какая-то конкретная логика.

Реализация

Мы будем использовать шаблон проектирования «Цепочка ответственности» для реализации настраиваемого рабочего процесса ведения журнала в приложении.

Допустим, нам нужно 3 разных уровня журнала в зависимости от их важности:

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

В этом случае наш запрос представляет собой сообщение журнала с его содержанием и уровнем журнала. Наши обработчики - логгеры отладки, информации и ошибок со своей собственной логикой. Чтобы реализовать желаемый рабочий процесс, мы могли бы связать регистраторы в следующем порядке: Отладка - ›Информация -› Ошибка. Если уровень журнала регистратора ниже или равен уровню, определенному в сообщении, сообщение должно быть зарегистрировано. Вот и все, действительно, все так просто!

Картинка стоит тысячи слов, поэтому давайте сначала проверим диаграмму классов, а затем реализуем шаблон.

Диаграмма классов

На диаграмме классов ниже показана реализация шаблона проектирования «Цепочка ответственности»:

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

Класс LogMessage используется для хранения информации о сообщении журнала: его уровень журнала и текст сообщения. Он также предоставляет общедоступный метод getFormattedMessage () для форматирования записи журнала как объекта Widget (для этого используется частный вспомогательный метод getLogEntryColor () и геттер logLevelString ).

LoggerBase - это абстрактный класс, который используется в качестве базового класса для всех конкретных средств ведения журнала:

  • logMessage () - регистрирует сообщение с помощью метода log () и передает запрос по цепочке;
  • logDebug () - записывает сообщение с уровнем журнала Debug;
  • logInfo () - записывает сообщение с уровнем журнала Info;
  • logError () - записывает сообщение с уровнем журнала Ошибка;
  • log () - абстрактный метод для регистрации сообщения (должен быть реализован конкретным регистратором).

Кроме того, LoggerBase содержит ссылку на следующее средство ведения журнала (nextLogger) и уровень ведения журнала (logLevel).

DebugLogger, InfoLogger и ErrorLogger - это конкретные классы регистратора, которые расширяют класс LoggerBase и реализуют абстрактный журнал () метод. InfoLogger использует ExternalLoggingService для регистрации сообщений, ErrorLogger - MailService.

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

LogBloc хранит список журналов и предоставляет их через поток - outLogStream. Кроме того, он определяет метод log () для добавления нового журнала в список и уведомления подписчиков outLogStream обновленным списком записей журнала.

ChainOfResponsibilityExample создает цепочку средств ведения журнала и использует общедоступные методы, определенные в LoggerBase, для регистрации сообщений. Он также инициализируется и содержит экземпляр LogBloc для хранения записей журнала и последующего отображения их в пользовательском интерфейсе.

LogLevel

Особый вид класса - перечисление - для определения различных уровней журнала. Кроме того, определено LogLevelExtensions, в котором оператор ‹= переопределяется для сравнения, является ли один уровень журнала ниже или равен другому.

LogMessage

Простой класс для хранения информации о записи журнала: уровень журнала и сообщение. Кроме того, этот класс определяет частный метод получения logLevelString для возврата текстового представления определенного уровня журнала и частный метод getLogEntryColor () для возврата цвета записи журнала на основе журнала. уровень. Метод getFormattedMessage () возвращает отформатированную запись журнала в виде виджета Text, который используется в пользовательском интерфейсе.

LogBloc

Класс компонента бизнес-логики (BLoC) для хранения сообщений журнала и предоставления их пользовательскому интерфейсу через общедоступный поток. Новые записи журнала добавляются в список журналов с помощью метода log (), в то время как все журналы могут быть доступны через общедоступный outLogStream.

ExternalLoggingService

Простой класс, представляющий фактическую внешнюю службу ведения журнала. Вместо отправки сообщения журнала какой-то сторонней службе ведения журнала (которая, на самом деле, может быть вызвана в методе logMessage ()), она просто записывает сообщение в пользовательский интерфейс через LogBloc.

MailService

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

LoggerBase

Абстрактный класс для базовой реализации регистратора. Он хранит уровень журнала и ссылку (преемника) на следующий регистратор в цепочке. Кроме того, в классе реализован общий метод logMessage (), который регистрирует сообщение, если его уровень журнала ниже, чем у текущего средства ведения журнала, а затем пересылает сообщение преемнику (если он есть). Абстрактный метод log () должен быть реализован определенными средствами ведения журнала, расширяющими класс LoggerBase.

Бетонные лесорубы

  • DebugLogger - конкретная реализация регистратора, которая устанавливает уровень журнала на Debug и просто записывает сообщение в пользовательский интерфейс через LogBloc.

  • InfoLogger - конкретная реализация регистратора, которая устанавливает уровень журнала на Info и использует ExternalLoggingService для регистрации сообщения.

  • ErrorLogger - конкретная реализация регистратора, которая устанавливает уровень журнала на Ошибка и использует MailService для регистрации сообщения.

Пример

Прежде всего, подготавливается файл разметки, который предоставляется как описание шаблона:

Виджет ChainOfResponsibilityExample инициализируется и содержит объект цепочки регистраторов (см. Метод initState ()). Кроме того, в демонстрационных целях там же инициализируется объект LogBloc, который используется для отправки журналов и получения их списка через поток - outLogStream.

Создавая цепочку регистраторов, клиент - виджет ChainOfResponsibilityExample - не заботится о деталях того, какой конкретный регистратор должен обрабатывать запись журнала, он просто передает (регистрирует) сообщение в цепочку регистраторов. Таким образом, отправитель (клиент) и получатель (регистратор) разделены, сама цепочка регистраторов может быть построена во время выполнения в любом порядке или структуре, например. вы можете пропустить средство ведения журнала отладки для нелокальных сред и использовать только цепочку Информация - ›Ошибка.

Как видно из примера, журналы уровня отладки обрабатываются только средством ведения журнала отладки, info - обработчиками уровня отладки и информации и error журналы обрабатываются всеми тремя регистраторами.

Все изменения кода для шаблона проектирования Цепочка ответственности и пример его реализации можно найти здесь.

Другие статьи из этой серии

Ваш вклад

👏 Нажмите кнопку хлопка ниже, чтобы выразить свою поддержку и побудить меня писать лучше!
💬 Оставьте отзыв на эту статью, поделившись своими мыслями, комментариями или пожеланиями относительно серии.
📢 Поделитесь этой статьей со своими друзья, коллеги в социальных сетях.
➕ Подписывайтесь на меня на Medium.
⭐ Пометьте репозиторий Github.