CodeLAB
на главную карта сайта обратная связь
каталог | задачи | паттерны | исходники | стат | форумы | ссылки
 гость
искать в
Главная >> Каталог задач >> Паттерны >> Поведения >> Цепочка обязанностей (Chain of Responsibility)

<< назад
распечатать обсудить >>


Цепочка обязанностей (Chain of Responsibility)
реализации: java, количество: 4

Aвтор: this
Дата: 04.10.2007
Просмотров: 50824
Рейтинг: 0/0,0(0)
+
реализации(исходники) +добавить

Имя

«Паттерн Chain of Responsibility»
codelab.ru codelab.ru источник оригинал

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

Условия, Задача, Назначение

Позволяет избежать привязки отправителя запроса к его получателю, давая шанс обработать запрос нескольким объектам. Связывает объекты-получатели в цепочку и передает запрос вдоль этой цепочки, пока его один из получателей его не обработает. источник codelab.ru оригинал codelab.ru
Возникает очень гибкая и свободная схема процесса обработки какого-либо запроса: в глобальном объекте какого-нибудь самого верхнего уровня можно определить алгоритм обработки по-умолчанию (в результате чего система уже может быть запущена, только все запросы вне зависимости от их содержания и контекста будут обрабатываться единым способом), а потом лишь там где требуется специальное обслуживание запроса - просто добавлять реализацию методов обработки у соответствующих более низких и детализированных слоев системы.
источник codelab.ru codelab.ru оригинал

Мотивация

Рассмотрим контекстно-зависимую оперативную справку в графическом интерфейсе пользователя, который может получить дополнительную информацию по любой части интерфейса, просто щелкнув на ней мышью. Содержание справки зависит от того, какая часть интерфейса и в каком контексте выбрана. Например, справка по кнопке в диалоговом окне может отличаться от справки по аналогичной кнопке в главном окне приложения. Если для некоторой части интерфейса справки нет, то система должна показать информацию о ближайшем контексте, в котором она находится, например, о диалоговом окне в целом. оригинал источник codelab.ru codelab.ru
Поэтому естественно было бы организовать возможность обработки справочной информации при следовании от более конкретных разделов к более общим. Кроме того, ясно, что запрос на получение справки обрабатывается одним из нескольких объектов пользовательского интерфейса, каким именно - зависит от контекста и имеющейся в наличии информации. codelab.ru источник codelab.ru оригинал
Проблема в том, что объект, инициирующий запрос (например, кнопка), не располагает информацией о том, какой объект в конечном итоге предоставит справку. источник оригинал codelab.ru codelab.ru
Нам необходим какой-то способ отделить кнопку-инициатор запроса от объектов, владеющих справочной информацией. Как этого добиться, показывает паттерн цепочка обязанностей. codelab.ru оригинал источник codelab.ru
Идея заключается в том, чтобы разорвать связь между отправителями и получателями, дав возможность обработать запрос нескольким объектам. Запрос перемещается по цепочке объектов, пока один из них не обработает его. codelab.ru источник оригинал codelab.ru
Первый объект в цепочке получает запрос и либо обрабатывает его сам, либо направляет следующему кандидату в цепочке (расположенному на более верхнем уровне иерархии и обычно включающему в себя сам этот предыдущий объект), который ведет себя точно так же. codelab.ru оригинал источник codelab.ru
У объекта, отправившего запрос, отсутствует информация об обработчике. Мы только можем сказать, что у запроса есть анонимный получатель (implicit receiver). оригинал codelab.ru codelab.ru источник

codelab.ru источник codelab.ru оригинал

Предположим, что пользователь запрашивает справку по кнопке Print (печать). Она находится в диалоговом окне PrintDialog, содержащем информацию об объекте приложения, которому принадлежит (см. предыдущую диаграмму объектов). На представленной диаграмме взаимодействий показано, как запрос на получение справки перемещается по цепочке. codelab.ru codelab.ru источник оригинал

handler in action источник codelab.ru оригинал codelab.ru

В данном случае ни кнопка aPrintButton, ни окно aPrintDialog не обрабатывают запрос, он достигает объекта anApplication, который может его обработать или игнорировать. У клиента, инициировавшего запрос, нет прямой ссылки на объект, который его в конце концов выполнит. источник codelab.ru оригинал codelab.ru
Чтобы отправить запрос по цепочке и гарантировать анонимность получателя, все объекты в цепочке имеют единый интерфейс для обработки запросов и для доступа к своему преемнику (следующему объекту в цепочке). Например, в системе оперативной справки можно было бы определить класс HelpHandler (предок классов всех объектов-кандидатов) с операцией HandleHelp. Тогда классы, которые будут обрабатывать запрос, смогут его передать своему родителю. источник codelab.ru оригинал codelab.ru
Для обработки запросов на получение справки классы Button, Dialog и Application пользуются операциями HelpHandler. По умолчанию операция HandleHelp просто перенаправляет запрос своему преемнику. В подклассах эта операция замещается, так что при благоприятных обстоятельствах может выдаваться справочная информация. В противном случае запрос отправляется дальше посредством реализации по умолчанию. codelab.ru codelab.ru оригинал источник

help handlers codelab.ru источник оригинал codelab.ru

Признаки применения, использования паттерна Цепочка обязанностей (Chain of Responsibility)

Используйте цепочку обязанностей, когда: источник codelab.ru codelab.ru оригинал
  1. Есть более одного объекта, способного обработать запрос, при этом настоящий обработчик заранее неизвестен и должен быть найден в соответствие с какой-либо кастомной логикой. codelab.ru источник оригинал codelab.ru
  2. Нужно отправить запрос одному из нескольких объектов, не указывая явно, какому именно. оригинал источник codelab.ru codelab.ru
  3. Набор объектов, способных обработать запрос, должен определяться динамически. codelab.ru источник codelab.ru оригинал

Решение

codelab.ru источник оригинал codelab.ru

codelab.ru источник codelab.ru оригинал

Типичная структура цепочки: источник codelab.ru оригинал codelab.ru

chain of handlers источник оригинал codelab.ru codelab.ru

Участники паттерна Цепочка обязанностей (Chain of Responsibility)

источник codelab.ru codelab.ru оригинал
источник codelab.ru codelab.ru оригинал
  1. Handler (HelpHandler) – обработчик.
    Определяет интерфейс для обработки запросов.
    Реализует связь с преемником (необязательно). источник codelab.ru оригинал codelab.ru
  2. ConcreteHandler (PrintButton, PrintDialog) - конкретный обработчик.
    Обрабатывает запрос, за который отвечает.
    Имеет доступ к своему преемнику.
    Если ConcreteHandler способен обработать запрос, то так и делает, если не может, то направляет его - его своему преемнику вверх по цепочке. оригинал источник codelab.ru codelab.ru
  3. Client – клиент.
    Отправляет запрос некоторому объекту ConcreteHandler в цепочке. источник codelab.ru codelab.ru оригинал

Схема использования паттерна Цепочка обязанностей (Chain of Responsibility)

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

Вопросы, касающиеся реализации паттерна Цепочка обязанностей (Chain of Responsibility)

При рассмотрении цепочки обязанностей следует обратить внимание на следующие моменты: источник codelab.ru оригинал codelab.ru
  1. Реализация цепочки преемников.
    Есть два способа реализовать такую цепочку: определить новые связи (обычно это делается в классе Handler, но можно и в ConcreteHandler), либо использовать существующие.
    До сих пор в наших примерах определялись новые связи, однако можно воспользоваться уже имеющимися ссылками на объекты для формирования цепочки преемников. Например, ссылка на родителя в иерархии «часть-целое» (паттерн компоновщик) может заодно определять и преемника «части». В структуре виджетов такие связи тоже могут существовать. В разделе, посвященном паттерну компоновщик, ссылки на родителей обсуждаются более подробно.
    Существующие связи можно использовать, когда они уже поддерживают нужную цепочку. Тогда мы избежим явного определения новых связей и сэкономим память. Но если структура не отражает устройства цепочки обязанностей, то уйти от определения избыточных связей не удастся. codelab.ru оригинал codelab.ru источник
  2. Соединение преемников.
    Если готовых ссылок, пригодных для определения цепочки, нет, то их придется ввести. В таком случае класс Handler не только определяет интерфейс запросов, но еще и хранит ссылку на преемника. Следовательно у обработчика появляется возможность определить реализацию операции HandleRequest по умолчанию - перенаправление запроса преемнику (если таковой существует). Если подкласс ConcreteHandler не заинтересован в запросе, то ему и не надо замещать эту операцию, поскольку по умолчанию запрос как раз и отправляется дальше. Вот пример базового класса HelpHandler, в котором хранится указатель на преемника:
     Базовый ConcreteHandler c ссылкой на приемника [java]  ссылка
    1. package chainOfResponsibility;
    2.  
    3. public class HelpHandler {
    4. HelpHandler successor;
    5.  
    6. public HelpHandler(HelpHandler successor) {
    7. super();
    8. this.successor = successor;
    9. }
    10.  
    11. public void HandleHelp() {
    12. if (successor != null) {
    13. successor.HandleHelp();
    14. }
    15. }
    16. }
    источник codelab.ru codelab.ru оригинал
  3. Представление запросов.
    Представлять запросы можно по-разному. В простейшей форме, например в случае класса HandleHelp, запрос жестко кодируется как вызов соответствующего дополнительного метода этого класса. Это удобно и безопасно, но переадресовывать тогда можно только фиксированный набор запросов, для которых в классе Handler определены специальные методы.
    Альтернатива - использовать единую функцию-обработчик, которой передается код запроса (скажем, целое число или строка). Так можно поддержать заранее неизвестное число запросов. Единственное требование состоит в том, что отправитель и получатель должны договориться о способе кодирования запроса.
    Это более гибкий подход, но при реализации нужно использовать условные операторы для распределения запросов по коду их обработки. Кроме того, не существует безопасного с точки зрения типов способа передачи параметров, поэтому упаковывать и распаковывать их приходится вручную. Очевидно, что это не так безопасно, как прямой вызов операции.
    Чтобы решить проблему передачи параметров, допустимо использовать отдельные объекты-запросы, в которых инкапсулированы параметры запроса. Т.е. специальный класс Request, например, может представлять некоторые запросы явно, а их новые типы описываются в подклассах. Подкласс может определить другие параметры. Обработчик должен иметь информацию о типе запроса (какой именно подкласс Request используется), чтобы разобрать эти параметры.
    Для идентификации запроса в классе Request можно определить функцию доступа, которая возвращает идентификатор класса. Вместо этого получатель мог бы воспользоваться информацией о типе, доступной во время выполнения, если язык программирования поддерживает такую возможность (в случае Java-ы для этих целей можно б было использовать результат выполнения .getClass().getName()).
    Приведем пример функции диспетчеризации, в которой используются объекты для идентификации запросов. Операция GetKind, указанная в базовом классе Request, определяет вид запроса:
     единая функция переадресации запросов [java]  ссылка
    1. package chainOfResponsibility;
    2.  
    3. public class Handler {
    4.  
    5. public void HandleRequest(Request req) {
    6. switch (req.GetKind()) {
    7. case Request.HELP:
    8. HandleHelp(req);
    9. break;
    10. case Request.PRINT:
    11. HandlePrint(req);
    12. break;
    13. default:
    14. // ...
    15. break;
    16. }
    17. }
    18.  
    19. protected void HandlePrint(Request req) {
    20. // ...
    21. }
    22.  
    23. protected void HandleHelp(Request req) {
    24. // ... }
    25. }

    И подклассы могут расширить схему диспетчеризации, переопределив операцию HandleRequest. Подкласс обрабатывает лишь те запросы, в которых заинтересован, а остальные отправляет родительскому классу. В этом случае подкласс именно расширяет, а не замещает операцию HandleRequest. Например, подкласс ExtendedHandler расширяет операцию HandleRequest, определенную в классе Handler, следующим образом:
     расширение схемы диспетчеризации подклассами [java]  ссылка
    1. package chainOfResponsibility;
    2.  
    3. public class ExtendedHandler extends Handler {
    4.  
    5. public void HandleRequest(Request req) {
    6. switch (req.GetKind()) {
    7. case Request.PREVIEW:
    8. HandlerPreview(req);
    9. break;
    10. default:
    11. super.HandleHelp(req);
    12. break;
    13. }
    14. }
    15.  
    16. protected void HandlerPreview(Request req) {
    17. // ...
    18.  
    19. }
    20. }
    источник codelab.ru оригинал codelab.ru
  4. Автоматическое перенаправление запросов специфическими средствами языка.
    Такими как, например, анонимная функция __call() в PHP, начиная с 5-ой версии, автоматически вызываемая средой языка если происходит попытка вызвать у объекта несуществующий в нем метод.
    В Smalltalk для этих целей используется механизм doesNotUnderstand.
    В любом случае мы получаем функционал, при котором сообщения, не имеющие соответствующих методов, перехватываются реализацией __call(), doesNotUnderstand, которая может быть замещена для перенаправления сообщения объекту-преемнику. Поэтому осуществлять перенаправление вручную уже не придется.
    Класс обрабатывает только запросы, в которых заинтересован, и ожидает, что механизм doesNotUnderstand()/__call() выполнит обработку по-умолчанию для всех остальных запросов. оригинал источник codelab.ru codelab.ru

Результаты

Паттерн цепочка обязанностей имеет следующие достоинства и недостатки: codelab.ru codelab.ru оригинал источник
  1. Ослабление связанности.
    Этот паттерн освобождает объект от необходимости «знать», кто конкретно обработает его запрос. Отправителю и получателю ничего неизвестно друг о друге, а включенному в цепочку объекту - о структуре цепочки.
    Таким образом, цепочка обязанностей помогает упростить взаимосвязи между объектами. Вместо того чтобы хранить ссылки на все объекты, которые могут стать получателями запроса, объект должен располагать информацией лишь о своем ближайшем преемнике. codelab.ru источник codelab.ru оригинал
  2. Дополнительная гибкость при распределении обязанностей между объектами.
    Цепочка обязанностей позволяет повысить гибкость распределения обязанностей между объектами. Добавить или изменить обязанности по обработке запроса можно, включив в цепочку новых участников или изменив ее каким-то другим образом. Этот подход можно сочетать со статическим порождением подклассов для создания специализированных обработчиков. codelab.ru оригинал codelab.ru источник
  3. Получение не гарантировано.
    Поскольку у запроса нет явного получателя, то нет и гарантий, что он вообще будет обработан: он может достичь конца цепочки и пропасть. Необработанным запрос может оказаться и в случае неправильной конфигурации цепочки. источник оригинал codelab.ru codelab.ru

Пример

В языке программирования Java использование Паттерн цепочка обязанностей самым непосредственным образом включено в ядро языка. Я, например, понял это в следующий момент, после того, когда подумал о каком-нибудь примере этого паттерна на java. Если еще не догадались, то речь идет о системе обработки исключений языка, т.е. exception-ов. Как известно, там имеется специальный тип исключений – объявляемые исключения (checked exceptions). Кратко: если какой-то метод объявляет такое исключение, то любой другой метод, который его вызывает должен либо перехватить и обработать это исключение, либо объявить его в своей сигнатуре, таким образом, передавая эту обязанность вверх по цепочке вызовов. Т.е здесь имеем, что в качестве запроса выступает исключение (ошибка, исключительная ситуация времени выполнения), которое может выброситься любым участком кода во время выполнения программы. Выражаясь терминами паттерна, этим отсылается запрос на обработку этого исключения вверх по всей иерархии вызовов этого метода. Наиболее ответственный методов в этой цепочке вызовов и обрабатывает эту ошибку, прекращая ее следование вверх по иерархии, в противном случае если она не будет перехвачена – в конце концов вызывается специальный метод, по-умолчанию, печатающий стек-трейс ошибки в консоль. источник codelab.ru codelab.ru оригинал
  codelab.ru codelab.ru оригинал источник
Представим, что у нас имеется некоторый модуль отображения списка новостей. codelab.ru оригинал codelab.ru источник

У новости есть заголовок, текст и собственно дата новости: News. codelab.ru оригинал codelab.ru источник

Новости хранятся в базе данных и для того, чтобы их оттуда доставать любые модули, совершенно ничего не знающие ни о каких базах, определена сущность DAO (Data Access Object), у которой имеются 2 необходимых метода: достать одну последнюю новость или список всех новостей: DAO_I. источник codelab.ru codelab.ru оригинал

В зависимости от конкретной СУБД, реализация этого интерфейса может, конечно, отличаться (разный текст sql-запросов и т.д.), для нашей базы подходит следующий: DAO. codelab.ru источник оригинал codelab.ru
  codelab.ru источник оригинал codelab.ru
Почти любое обращение к базе может сгенерировать объявляемое исключение SQLException, и мы, соответственно, можем либо тут же его перехватить и обработать, либо объявить в сигнатуре метода, передав эту ответственность вызвавшему методу верхнего уровня. В методе GetAllNews() выполняется PreparedStatement, выводящий список всех новостей. В случае ошибки выборки списка новостей система должна сразу уведомить об этом пользователя, поэтому класс DAO тут же обрабатывает этот запрос и выводит информацию об ошибке в консоль пользователя. оригинал источник codelab.ru codelab.ru
С методом GetLatestNews(), получающим самую свежую новость – ситуация обратная. Для нашей системы, например, главной функцией является все же функция вывода списка новостей за день, а не отображение самой последней новости, поэтому данный метод не должен таким же способом обрабатывать эту ошибку и мы делаем более гибкое решение, передавая ответственность за это вверх по цепочке (объявляя SQLException во throws этого метода). codelab.ru источник codelab.ru оригинал
Ну и главный класс нашего модуля это NewsPage, связывающий все это воедино: NewsPage. codelab.ru источник codelab.ru оригинал
Как видим метод Draw, отображающий всю информацию, принимает на себя ответственность за обработку исключения об ошибке выборки свежей новости – а именно просто делает запись об этом в логе. Обрабатывать исключение об ошибке выборке всех новостей – ему уже не нужно, поскольку это было сделано слоем ниже, в классе DAO. codelab.ru источник codelab.ru оригинал
источник codelab.ru оригинал codelab.ru

  источник codelab.ru оригинал codelab.ru

Известные применения паттерна Цепочка обязанностей (Chain of Responsibility)

Паттерн цепочка обязанностей используется в нескольких библиотеках классов для обработки событий, инициированных пользователем. Класс Handler в них называется по-разному, но идея всегда одна и та же: когда пользователь щелкает кнопкой мыши или нажимает клавишу, генерируется некоторое событие, которое распространяется по цепочке. В МасАрр и ЕТ++ класс называется EventHandler, в библиотеке TCL[google] фирмы Symantec - Bureaucrat, а в библиотеке из системы [google=NeXT Responder]NeXTResponder. оригинал codelab.ru codelab.ru источник
В каркасе графических редакторов Unidraw определены объекты Command, которые инкапсулируют запросы к объектам Component и ComponentView. Объекты Command - это запросы, которые компонент или вид компонента могут интерпретировать как команду на выполнение определенной операции. codelab.ru оригинал codelab.ru источник
Это соответствует подходу «запрос как объект», описанному в разделе «Реализация». Компоненты и виды компонентов могут быть организованы иерархически. codelab.ru codelab.ru оригинал источник
Как компонент, так и его вид могут перепоручать интерпретацию команды своему codelab.ru источник оригинал codelab.ru
родителю, тот - своему родителю и так далее, то есть речь идет о типичной цепочке обязанностей. codelab.ru оригинал источник codelab.ru
В ЕТ++ паттерн цепочка обязанностей применяется для обработки запросов на обновление графического изображения. Графический объект вызывает операцию InvalidateRect всякий раз, когда возникает необходимость обновить часть занимаемой им области. Но выполнить эту операцию самостоятельно графический объект не может, так как не имеет достаточной информации о своем контексте, например из-за того, что окружен такими объектами, как Scroller (полоса прокрутки) или Zoomer (лупа), которые преобразуют его систему координат. Это означает, что объект может быть частично невидим, так как он оказался за границей области прокрутки или изменился его масштаб. Поэтому реализация InvalidateRect по умолчанию переадресует запрос контейнеру, где находится соответствующий объект. Последний объект в цепочке обязанностей — экземпляр класса Window. Гарантируется, что к тому моменту, как Window получит запрос, недействительный прямоугольник будет трансформирован правильно. Window источник codelab.ru codelab.ru оригинал
обрабатывает InvalidateRect, послав запрос интерфейсу оконной системы и требуя тем самым выполнить обновление. codelab.ru оригинал codelab.ru источник
codelab.ru источник оригинал codelab.ru

Родственные паттерны

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


Реализации: java(4)   +добавить реализацию

1) News.java, code #483[автор:this]
2) DAO_I.java, code #484[автор:this]
3) DAO.java, code #485[автор:this]
4) NewsPage.java, code #486[автор:this]


<< назад наверх
распечатать обсудить >>

 
каталог | задачи | паттерны | исходники | стат | форумы | карта сайта | контакты | ссылки 
© 2000-2017 CodeLAB Group
  Все права защищены
Страница сгенерирована за 0.043838 секунд
Количество запросов к БД: 14, gzip: 22.0kb/105.5kb(80%)