Схема цепочки ответственности - Chain-of-responsibility pattern

В объектно-ориентированный дизайн, то схема цепочки ответственности это шаблон дизайна состоящий из источника командные объекты и ряд объекты обработки.[1] Каждый объект обработки содержит логику, которая определяет типы командных объектов, которые он может обрабатывать; остальные передаются следующему обрабатывающему объекту в цепочке. Также существует механизм для добавления новых объектов обработки в конец этой цепочки.

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

Этот паттерн продвигает идею Слабая связь.

Схема цепочки ответственности структурно почти идентична схеме декоратор шаблон с той разницей, что для декоратора все классы обрабатывают запрос, а для цепочки ответственности только один из классов в цепочке обрабатывает запрос. Это строгое определение концепции ответственности в GoF книга. Однако многие реализации (например, средства ведения журнала, описанные ниже, обработка событий пользовательского интерфейса, фильтры сервлетов в Java и т. Д.) Позволяют нескольким элементам в цепочке брать на себя ответственность.

Обзор

Цепочка ответственности [2]шаблон дизайна - один из двадцати трех хорошо известных Шаблоны проектирования GoF которые описывают общие решения повторяющихся проблем проектирования при разработке гибкого и многократно используемого объектно-ориентированного программного обеспечения, то есть объектов, которые легче реализовать, изменить, протестировать и повторно использовать.

Какие проблемы может решить шаблон проектирования «Цепочка ответственности»? [3]

  • Следует избегать привязки отправителя запроса к получателю.
  • Должно быть возможно, что более одного получателя могут обработать запрос.

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

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

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

Это позволяет нам отправлять запрос цепочке получателей, не зная, какой из них обрабатывает запрос. Запрос передается по цепочке до тех пор, пока получатель не обработает запрос. Отправитель запроса больше не связан с конкретным получателем.

См. Также схему классов и последовательности UML ниже.

Структура

Схема классов и последовательности UML

Пример класса UML и диаграммы последовательности для шаблона проектирования «Цепочка ответственности». [4]

В приведенном выше UML диаграмма классов, то Отправитель class не относится напрямую к конкретному классу получателя. Отправитель относится к Обработчик интерфейс для обработки запроса (handler.handleRequest ()), что делает Отправитель независимо от того, какой получатель обрабатывает запрос. Приемник1, Приемник2, и Приемник3 классы реализуют Обработчик интерфейс путем обработки или пересылки запроса (в зависимости от условий выполнения).
В UML схема последовательности показывает взаимодействия во время выполнения: В этом примере Отправитель вызовы объектов handleRequest () на приемник1 объект (типа Обработчик). В приемник1 пересылает запрос приемник2, который, в свою очередь, перенаправляет запрос на приемник3, который обрабатывает (выполняет) запрос.

Пример

Пример Java

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

импорт java.util.Arrays;импорт java.util.EnumSet;импорт java.util.function.Consumer;@FunctionalInterfaceобщественный интерфейс Регистратор {    общественный перечислить LogLevel {        ИНФОРМАЦИЯ, ОТЛАЖИВАТЬ, ПРЕДУПРЕЖДЕНИЕ, ОШИБКА, FUNCTIONAL_MESSAGE, FUNCTIONAL_ERROR;        общественный статический LogLevel[] все() {            возвращаться значения();        }    }    Абстрактные пустота сообщение(Нить сообщение, LogLevel строгость);    дефолт Регистратор appendNext(Регистратор nextLogger) {        возвращаться (сообщение, строгость) -> {            сообщение(сообщение, строгость);            nextLogger.сообщение(сообщение, строгость);        };    }    статический Регистратор writeLogger(LogLevel[] уровни, Потребитель<Нить> stringConsumer) {        EnumSet<LogLevel> набор = EnumSet.копия(Массивы.asList(уровни));        возвращаться (сообщение, строгость) -> {            если (набор.содержит(строгость)) {                stringConsumer.принимать(сообщение);            }        };    }    статический Регистратор consoleLogger(LogLevel... уровни) {        возвращаться writeLogger(уровни, сообщение -> Система.ошибаться.println("Запись в консоль:" + сообщение));    }    статический Регистратор emailLogger(LogLevel... уровни) {        возвращаться writeLogger(уровни, сообщение -> Система.ошибаться.println("Отправка по электронной почте:" + сообщение));    }    статический Регистратор fileLogger(LogLevel... уровни) {        возвращаться writeLogger(уровни, сообщение -> Система.ошибаться.println(«Запись в файл журнала:» + сообщение));    }    общественный статический пустота главный(Нить[] аргументы) {        // Построить неизменную цепочку ответственности        Регистратор регистратор = consoleLogger(LogLevel.все())                .appendNext(emailLogger(LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR))                .appendNext(fileLogger(LogLevel.ПРЕДУПРЕЖДЕНИЕ, LogLevel.ОШИБКА));        // Обрабатывается consoleLogger, поскольку у консоли есть LogLevel всех        регистратор.сообщение(«Вход в функцию ProcessOrder ()»., LogLevel.ОТЛАЖИВАТЬ);        регистратор.сообщение("Запись заказа получена"., LogLevel.ИНФОРМАЦИЯ);        // Обрабатывается consoleLogger и emailLogger, поскольку emailLogger реализует Functional_Error и Functional_Message        регистратор.сообщение(«Невозможно обработать заказ ORD1 от D1 для клиента C1»., LogLevel.FUNCTIONAL_ERROR);        регистратор.сообщение("Заказ отправлен.", LogLevel.FUNCTIONAL_MESSAGE);        // Обрабатывается consoleLogger и fileLogger, поскольку fileLogger реализует предупреждения и ошибки        регистратор.сообщение(«Сведения об адресе клиента отсутствуют в базе данных филиала»., LogLevel.ПРЕДУПРЕЖДЕНИЕ);        регистратор.сообщение(«Сведения об адресе клиента отсутствуют в базе данных организации»., LogLevel.ОШИБКА);    }}

Пример C #

В этом примере C # приложение ведения журнала используется для выбора различных источников в зависимости от уровня журнала;

пространство имен ChainOfResponsibility{    [Флаги]    общественный перечислить LogLevel    {        Никто = 0,                 //        0        Информация = 1,                 //        1        Отлаживать = 2,                //       10        Предупреждение = 4,              //      100        Ошибка = 8,                //     1000        Функциональное сообщение = 16,   //    10000        FunctionalError = 32,     //   100000        Все = 63                  //   111111    }     /// <резюме>    /// Абстрактный обработчик в шаблоне цепочки ответственности.    ///     общественный Абстрактные учебный класс Регистратор    {        защищенный LogLevel logMask;         // Следующий обработчик в цепочке        защищенный Регистратор следующий;         общественный Регистратор(LogLevel маска)        {            это.logMask = маска;        }         /// <резюме>        /// Устанавливает следующий регистратор для создания списка / цепочки обработчиков.        ///         общественный Регистратор SetNext(Регистратор следующий блоггер)        {            Регистратор lastLogger = это;            пока (lastLogger.следующий != ноль)            {                lastLogger = lastLogger.следующий;            }            lastLogger.следующий = следующий блоггер;            возвращаться это;        }         общественный пустота Сообщение(нить сообщение, LogLevel строгость)        {            если ((строгость & logMask) != 0) // Истина, только если какой-либо из битов logMask установлен как серьезность            {                Напиши сообщение(сообщение);            }            если (следующий != ноль)             {                следующий.Сообщение(сообщение, строгость);             }        }         Абстрактные защищенный пустота Напиши сообщение(нить сообщение);    }     общественный учебный класс ConsoleLogger : Регистратор    {        общественный ConsoleLogger(LogLevel маска)            : основание(маска)        { }         защищенный отменять пустота Напиши сообщение(нить сообщение)        {            Консоль.WriteLine("Запись в консоль:" + сообщение);        }    }     общественный учебный класс EmailLogger : Регистратор    {        общественный EmailLogger(LogLevel маска)            : основание(маска)        { }         защищенный отменять пустота Напиши сообщение(нить сообщение)        {            // Заполнитель для логики отправки почты, обычно конфигурации электронной почты сохраняются в файле конфигурации.            Консоль.WriteLine("Отправка по электронной почте:" + сообщение);        }    }     учебный класс FileLogger : Регистратор    {        общественный FileLogger(LogLevel маска)            : основание(маска)        { }         защищенный отменять пустота Напиши сообщение(нить сообщение)        {            // Заполнитель для логики записи файла            Консоль.WriteLine(«Запись в файл журнала:» + сообщение);        }    }     общественный учебный класс Программа    {        общественный статический пустота Главный(нить[] аргументы)        {            // Выстраиваем цепочку ответственности            Регистратор регистратор;            регистратор = новый ConsoleLogger(LogLevel.Все)                             .SetNext(новый EmailLogger(LogLevel.Функциональное сообщение | LogLevel.FunctionalError))                             .SetNext(новый FileLogger(LogLevel.Предупреждение | LogLevel.Ошибка));             // Обрабатывается ConsoleLogger, так как консоль имеет лог-уровень всех            регистратор.Сообщение(«Вход в функцию ProcessOrder ()»., LogLevel.Отлаживать);            регистратор.Сообщение("Запись заказа получена"., LogLevel.Информация);             // Обрабатывается ConsoleLogger и FileLogger, так как filelogger реализует предупреждения и ошибки            регистратор.Сообщение(«Сведения об адресе клиента отсутствуют в базе данных филиала»., LogLevel.Предупреждение);            регистратор.Сообщение(«Сведения об адресе клиента отсутствуют в базе данных организации»., LogLevel.Ошибка);             // Обрабатывается ConsoleLogger и EmailLogger, поскольку реализует функциональную ошибку            регистратор.Сообщение(«Невозможно обработать заказ ORD1 от D1 для клиента C1»., LogLevel.FunctionalError);             // Обрабатывается ConsoleLogger и EmailLogger            регистратор.Сообщение("Заказ отправлен.", LogLevel.Функциональное сообщение);        }    }} /* ВыходЗапись в консоль: Вход в функцию ProcessOrder ().Запись на консоль: получена запись заказа.Запись на консоль: сведения об адресе клиента отсутствуют в базе данных Branch.Запись в файл журнала: сведения об адресе клиента отсутствуют в базе данных Branch.Запись на консоль: сведения об адресе клиента отсутствуют в базе данных организации.Запись в файл журнала: сведения об адресе клиента отсутствуют в базе данных организации.Запись в консоль: Невозможно обработать заказ ORD1 от D1 для клиента C1.Отправка по электронной почте: Невозможно обработать заказ ORD1 от D1 для клиента C1.Запись в консоль: Заказ отправлен.Отправка по электронной почте: Заказ отправлен.*/

Пример кристалла

перечислить LogLevel  Никто  Информация  Отлаживать  Предупреждение  Ошибка  Функциональное сообщение  FunctionalError  ВсеконецАбстрактные учебный класс Регистратор  свойство log_levels  свойство следующий : Регистратор | Ноль  def инициализировать(*уровни)    @log_levels = [] из LogLevel    уровни.каждый делать |уровень|      @log_levels << уровень    конец  конец  def сообщение(сообщение : Нить, строгость : LogLevel)    если @log_levels.включает?(LogLevel::Все) || @log_levels.включает?(строгость)      Напиши сообщение(сообщение)    конец    @следующий.пытаться(&.сообщение(сообщение, строгость))  конец  Абстрактные def Напиши сообщение(сообщение : Нить)конецучебный класс ConsoleLogger < Регистратор  def Напиши сообщение(сообщение : Нить)    ставит "Запись в консоль: #{сообщение}"  конецконецучебный класс EmailLogger < Регистратор  def Напиши сообщение(сообщение : Нить)    ставит "Отправка по электронной почте: #{сообщение}"  конецконецучебный класс FileLogger < Регистратор  def Напиши сообщение(сообщение : Нить)    ставит "Запись в файл журнала: #{сообщение}"  конецконец# Программа# Построить цепочку ответственностирегистратор = ConsoleLogger.новый(LogLevel::Все)регистратор1 = регистратор.следующий = EmailLogger.новый(LogLevel::Функциональное сообщение, LogLevel::FunctionalError)регистратор2 = logger1.следующий = FileLogger.новый(LogLevel::Предупреждение, LogLevel::Ошибка)# Обрабатывается ConsoleLogger, так как консоль имеет лог-уровень всехрегистратор.сообщение(«Вход в функцию ProcessOrder ()»., LogLevel::Отлаживать)регистратор.сообщение("Запись заказа получена"., LogLevel::Информация)# Обрабатывается ConsoleLogger и FileLogger, поскольку filelogger реализует предупреждения и ошибкирегистратор.сообщение(«Сведения об адресе клиента отсутствуют в базе данных филиала»., LogLevel::Предупреждение)регистратор.сообщение(«Сведения об адресе клиента отсутствуют в базе данных организации»., LogLevel::Ошибка)# Обрабатывается ConsoleLogger и EmailLogger, поскольку реализует функциональную ошибкурегистратор.сообщение(«Невозможно обработать заказ ORD1 от D1 для клиента C1»., LogLevel::FunctionalError)# Обрабатывается ConsoleLogger и EmailLoggerрегистратор.сообщение("Заказ отправлен.", LogLevel::Функциональное сообщение)

Выход

Запись в консоль: ввод функции ProcessOrder (). Запись в консоль: получена запись о заказе. Запись в консоль: сведения об адресе клиента отсутствуют в базе данных Branch. Запись в файл журнала: сведения об адресе клиента отсутствуют в базе данных Branch. Запись в консоль: сведения об адресе клиента. отсутствует в базе данных организации. Запись в файл журнала: сведения об адресе клиента отсутствуют в базе данных организации. Запись в консоль: невозможно обработать заказ ORD1, датированный D1 для клиента C1. Отправка по электронной почте: Невозможно обработать заказ ORD1, датированный D1 для клиента C1. консоль: Заказ отправлен. Отправка по электронной почте: Заказ отправлен.

Пример Python

"""Пример схемы цепочки ответственности."""из abc импорт ABCMeta, абстрактный методиз перечислить импорт Enum, автоучебный класс LogLevel(Enum):    "" "Перечисление уровней журнала." ""    НИКТО = авто()    ИНФОРМАЦИЯ = авто()    ОТЛАЖИВАТЬ = авто()    ПРЕДУПРЕЖДЕНИЕ = авто()    ОШИБКА = авто()    FUNCTIONAL_MESSAGE = авто()    FUNCTIONAL_ERROR = авто()    ВСЕ = авто()учебный класс Регистратор:    "" "Абстрактный обработчик в шаблоне цепочки ответственности." ""    __metaclass__ = ABCMeta    следующий = Никто    def __в этом__(себя, уровни) -> Никто:        "" "Инициализировать новый регистратор.        Аргументы:            уровни (список [str]): Список уровней журнала.        """        себя.log_levels = []        за уровень в уровни:            себя.log_levels.добавить(уровень)    def set_next(себя, next_logger: Регистратор):        "" "Установить следующий ответственный регистратор в цепочке.        Аргументы:            next_logger (Регистратор): Следующий ответственный регистратор.        Возвращает: Регистратор: Следующий ответственный регистратор.        """        себя.следующий = next_logger        возвращаться себя.следующий    def сообщение(себя, сообщение: ул, строгость: LogLevel) -> Никто:        "" "Обработчик писателя сообщения.        Аргументы:            msg (str): строка сообщения.            severity (LogLevel): серьезность сообщения в виде перечисления уровня журнала.        """        если LogLevel.ВСЕ в себя.log_levels или же строгость в себя.log_levels:            себя.Напиши сообщение(сообщение)        если себя.следующий является нет Никто:            себя.следующий.сообщение(сообщение, строгость)    @abstractmethod    def Напиши сообщение(себя, сообщение: ул) -> Никто:        "" "Абстрактный метод написания сообщения.        Аргументы:            msg (str): строка сообщения.        Повышает: NotImplementedError        """        поднимать NotImplementedError(«Вам следует реализовать этот метод».)учебный класс ConsoleLogger(Регистратор):    def Напиши сообщение(себя, сообщение: ул) -> Никто:        "" "Переопределяет абстрактный метод родителя для записи в консоль.        Аргументы:            msg (str): строка сообщения.        """        Распечатать("Запись в консоль:", сообщение)учебный класс EmailLogger(Регистратор):    "" "Переопределяет родительский абстрактный метод для отправки электронной почты.    Аргументы:        msg (str): строка сообщения.    """    def Напиши сообщение(себя, сообщение: ул) -> Никто:        Распечатать(ж"Отправка по электронной почте: {msg}")учебный класс FileLogger(Регистратор):    "" "Переопределяет абстрактный метод родителя для записи файла.    Аргументы:        msg (str): строка сообщения.    """    def Напиши сообщение(себя, сообщение: ул) -> Никто:        Распечатать(ж"Запись в файл журнала: {msg}")def главный():    "" "Построение цепочки ответственности" ""    регистратор = ConsoleLogger([LogLevel.ВСЕ])    email_logger = регистратор.set_next(        EmailLogger([LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR])    )    # Поскольку нам не нужно использовать экземпляр файлового логгера в дальнейшем    # Мы не будем устанавливать для него никакого значения.    email_logger.set_next(        FileLogger([LogLevel.ПРЕДУПРЕЖДЕНИЕ, LogLevel.ОШИБКА])    )    # ConsoleLogger обработает эту часть кода, так как сообщение    # имеет уровень журнала всех    регистратор.сообщение(«Вход в функцию ProcessOrder ()»., LogLevel.ОТЛАЖИВАТЬ)    регистратор.сообщение("Запись заказа получена"., LogLevel.ИНФОРМАЦИЯ)    # ConsoleLogger и FileLogger будут обрабатывать эту часть, поскольку файловый логгер    # реализует ПРЕДУПРЕЖДЕНИЕ и ОШИБКА    регистратор.сообщение(        «Сведения об адресе клиента отсутствуют в базе данных филиала».,        LogLevel.ПРЕДУПРЕЖДЕНИЕ    )    регистратор.сообщение(        «Сведения об адресе клиента отсутствуют в базе данных организации».,        LogLevel.ОШИБКА    )    # ConsoleLogger и EmailLogger будут обрабатывать эту часть по мере реализации    # функциональная ошибка    регистратор.сообщение(        «Невозможно обработать заказ ORD1 от D1 для клиента C1».,        LogLevel.FUNCTIONAL_ERROR    )    регистратор.сообщение("Заказ отправлен.", LogLevel.FUNCTIONAL_MESSAGE)если __имя__ == "__главный__":    главный()

Реализации

Какао и прикосновение какао

В Какао и Какао Touch каркасы, используемые для OS X и iOS соответственно, приложения активно используют схему цепочки ответственности для обработки событий. Объекты, которые участвуют в цепочке, называются ответчик объекты, унаследованные от NSResponder (OS X) /UIResponder (iOS) класс. Все объекты просмотра (NSView/UIView), просмотреть объекты контроллера (NSViewController/UIViewController), оконные объекты (NSWindow/UIWindow) и объект приложения (NSApplication/UIAapplication) являются объектами-респондентами.

Как правило, когда представление получает событие, которое не может обработать, оно отправляет его своему супервизору, пока оно не достигнет контроллера представления или объекта окна. Если окно не может обработать событие, оно отправляется объекту приложения, который является последним объектом в цепочке. Например:

  • В OS X перемещение текстурированного окна с помощью мыши можно выполнять из любого места (не только из строки заголовка), если только в этом месте нет представления, которое обрабатывает события перетаскивания, например элементы управления ползунком. Если такого представления (или супервизора) нет, события перетаскивания отправляются вверх по цепочке в окно, которое обрабатывает событие перетаскивания.
  • В iOS обычно обрабатывают события представления в контроллере представления, который управляет иерархией представления, вместо того, чтобы создавать подклассы самого представления. Поскольку контроллер представления находится в цепочке респондента после всех своих управляемых подпредставлений, он может перехватывать любые события представления и обрабатывать их.

Смотрите также

Рекомендации

  1. ^ «Архивная копия». Архивировано из оригинал на 2018-02-27. Получено 2013-11-08.CS1 maint: заархивированная копия как заголовок (связь)
  2. ^ Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес (1994). Паттерны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования. Эддисон Уэсли. стр.223ff. ISBN  0-201-63361-2.CS1 maint: несколько имен: список авторов (связь)
  3. ^ «Шаблон проектирования цепочки ответственности - проблема, решение и применимость». w3sDesign.com. Получено 2017-08-12.
  4. ^ «Шаблон проектирования цепочки ответственности - структура и взаимодействие». w3sDesign.com. Получено 2017-08-12.