Паттерн Chain of Responsibility (цепочка обязанностей). Общие сведения. Реализация на C++
Содержание
- 1. Общие сведения о паттерне Chain of Responsibility (цепочка обязанностей)
- 2. Рисунок, демонстрирующий структуру паттерна Chain of Responsibility. Классы, необходимые для реализации паттерна
- 3. Преимущества и недостатки
- 4. Пример реализации паттерна «Цепочка обязанностей» на языке C++
- Связанные темы
Поиск на других ресурсах:
1. Общие сведения о паттерне Chain of Responsibility (цепочка обязанностей)
Паттерн Chain of Responsibility (цепочка обязанностей) относится к паттернам поведения объектов. Паттерн предназначен для создания цепочки объектов-получателей (обработчиков), которые получают и обрабатывают некоторый запрос от клиента. Клиент (клиентский объект) посылает запрос, который может быть обработан одним из объектов-получателей. Этот запрос рассматривается последовательно до его обработки.
Характерные черты паттерна:
- объект-клиент, который инициирует запрос, не владеет информацией о том, какой из объектов в цепи будет этот запрос обрабатывать. В запросе есть анонимный получатель (implicit receiver);
- нет прямой зависимости между отправителем и приемником. Запрос от отправителя пересматривается объектами-приемниками по цепочке до тех пор, пока не будет обработан;
- объект-обработчик обрабатывает запрос в зависимости от выполнения некоторого условия. Если обработчик не может обработать запрос, он передает его следующему объекту-обработчику;
- если в течение просмотра всей цепи обработчиков, запрос не обработан, то последний объект-обработчик в цепи может по особому обработать этот запрос.
Паттерн имеет следующие особенности реализации:
- создается иерархия классов, содержащих ссылки (указатели) на своего преемника (successor);
- на основе иерархии строится цепочка обработчиков. Здесь создаются объекты-обработчики, ссылающиеся на своего преемника (successor). ;
- создается объект-клиент, который будет отправлять запрос. Этот объект-клиент ссылается на «ближайший» объект-обработчик. Термин «ближайший» означает, что объекты-обработчики могут ссылаться друг на друга с учетом некоторой иерархии классов. Эта иерархия строится по принципу «от конкретного к общему»;
- объект-клиент посылает запрос путем вызова соответствующего метода (операции).
Взаимодействие между клиентом и объектами-обработчиками можно сформулировать следующими шагами:
- создать объекты, которые обрабатывают запрос и сформировать их в цепочку;
- создать клиента, который указывает на первый объект в цепи;
- инициировать запрос клиентом для его просмотра объектами-обработчиками на предмет обработки. Запрос продвигается по цепочке пока некоторый объект не обработает его.
Паттерн Chain of Responsibility используется в следующих случаях:
- когда существует более чем один объект, способный обработать запрос. Этот объект заранее неизвестен и должен быть найден автоматически;
- когда нужно отправить запрос одному объекту из набора объектов, при этом не зная, какой именно объект обработает запрос;
- когда набор объектов, которые должны обработать запрос, формируется динамически.
⇑
2. Рисунок, демонстрирующий структуру паттерна Chain of Responsibility. Классы, необходимые для реализации паттерна
На рисунке 1 изображена структура паттерна Chain of Responsibility.
Рисунок 1. Структура паттерна Chain of Responsibility
Как видно из рисунка 1, для использования паттерна нужно реализовать следующие классы:
- класс Handler — базовый класс, реализующий интерфейс для обработки запросов. Класс может реализовывать связь с последователем;
- классы ConcreteHandler1, ConcreteHandler2 …, ConcreteHandlerN которые есть конкретными обработчиками. Каждый из этих классов обрабатывает запрос, за который отвечает и имеет доступ к своему преемнику. Если запрос невозможно обработать, то происходит его передача преемнику;
- клиентский класс Client — отправляет запрос некоторому объекту в цепочке ConcreteHandler1, ConcreteHandler2 …, ConcreteHandlerN.
На рисунке 2 изображена типовая структура цепи объектов ConcreteHandler1, ConcreteHandler2 …, ConcreteHandlerN.
Рисунок 2. Структура объектов ConcreteHandler и организация «цепочки»
⇑
3. Преимущества и недостатки
Паттерн «цепочка обязанностей» имеет следующие преимущества:
- объект, обрабатывающий запрос, не знает ничего об отправителе и структуре цепочки;
- объект-клиент, посылающий запрос, не знает какой объект-получатель обработает запрос;
- упрощаются взаимосвязи между объектом который отправляет запрос и объектами, получающими этот запрос. Объект-отправитель имеет ссылку только на «ближайшего» преемника, ему не нужно хранить ссылки на другие объекты-получатели;
- цепочка обязанностей обеспечивает гибкость при распределении обязанностей между объектами. Паттерн позволяет добавлять новых участников в цепочку и менять их обязанности.
Недостатком данного паттерна является то, что запрос может быть вообще не обработан. Если запрос, пройдя всю цепочку, достигает последнего объекта-обработчика и не обрабатывается, то этот запрос исчезает. Такой случай может возникнуть в случае неправильного построения структуры цепочки.
⇑
4. Пример реализации паттерна «Цепочка обязанностей» на языке C++
В данном примере реализован паттерн Chain of Responsibility для структуры изображенной на рисунке 3.
Рисунок 3. Структура паттерна Chain of Responsibility для 3-х обработчиков, реализованного на C++
Программный код, реализующий паттерн Chain of Responsibility для трех конкретных обработчиков следующий
#include <iostream> using namespace std; // Паттерн - Цепочка обязанностей // Класс, который служит единым интерфейсом // для всех объектов, обрабатывающих запрос. // В этом классе указываются общие для объектов методы. // Класс определяет: // - внутреннее поле successor - указатель на преемника; // - конструктор Handler(); // - метод HandleRequest(), предназначенный для обработки запроса; // - метод DoRequest() - определяет, нужно ли выполнять запрос; // - метод SetHandler() - устанавливает возможность обработки конкретным обработчиком. class Handler { private: Handler* successor; // указатель на последователя (последователь) int numHandler; // номер обработчика, который обрабатывает запрос (1..3) public: // Конструктор Handler(Handler* _successor = nullptr, int numHandler = -1) : successor(_successor), numHandler(numHandler) { } // Виртуальный метод, предназначеный для обработки запроса. virtual void HandleRequest() { if (successor) successor->HandleRequest(); } // Метод, определящий, нужно ли выполнять запрос virtual bool DoRequest() { return numHandler != -1; } // Метод, устанавливающий обработчика и возможность обработки virtual void SetHandler(Handler* _successor, int _numHandler) { successor = _successor; numHandler = _numHandler; } }; // Конкретный обработчик - 1 class ConcreteHandler1 :Handler { public: // Конструктор - получает 2 параметра: // - указатель на базовый класс; // - значение, определяющее, можно ли обрабатывать запрос. ConcreteHandler1(Handler* handler, int numHandler = -1) : Handler(handler, numHandler) { } // Конкретный метод обработки virtual void HandleRequest() { // Проверка, обрабатывает ли данный обработчик запрос. if (DoRequest()) // Если обрабатывает, то выполнить соответствующие действия { cout << "ConcreteHandler1.HandleRequest" << endl; } else { // если не обрабатывает, то передать вызов // следующему обработчику по цепочке Handler::HandleRequest(); } } }; // Конкретный обработчик - 2 class ConcreteHandler2 :Handler { public: // Конструктор - получает 2 параметра: // - указатель на базовый класс; // - значение, определяющее можно ли обрабатывать запрос. ConcreteHandler2(Handler* handler, int numHandler = -1) : Handler(handler, numHandler) { } // Конкретный метод обработки virtual void HandleRequest() { // Проверка, обрабатывает ли данный обработчик запрос. if (DoRequest()) // Если обрабатывает, то выполнить соответствующие действия { cout << "ConcreteHandler2.HandleRequest" << endl; } else { // если не обрабатывает, то передать вызов // следующему обработчику по цепи Handler::HandleRequest(); } } }; // Последний конкретный обработчик. // Этот обработчик замыкает обработку. // Поэтому ему не нужно передавать ссылку // на предыдущего обработчика class ConcreteHandler3 :Handler { public: // Конструктор - получает 2 параметра: // - указатель на базовый класс; // - значение, определяющее можно ли обрабатывать запрос. ConcreteHandler3(int numHandler = -1) : Handler(nullptr, numHandler) { } // Конкретный метод обработки virtual void HandleRequest() { // Проверка, обрабатывает ли данный обработчик запрос. if (DoRequest()) // Если обрабатывает, то выполнить соответствующие действия { cout << "ConcreteHandler3.HandleRequest" << endl; } else { // если не обрабатывает, то вывести сообщение // о том, что в цепочке нету соответствующего обработчика. cout << "There is no corresponding handler in the chain." << endl; } } }; void main() { // Код клиента. // 1. Создать цепочку из трех обработчиков, задать им соответствующий номер обработки // 1.1. Первым создается последний обработчик в цепи ConcreteHandler3* h3 = new ConcreteHandler3(3); // 3 - номер обработчика в цепи // 1.2. Следующий номер 2, получает ссылку на предыдущего обработчика h3 ConcreteHandler2* h2 = new ConcreteHandler2((Handler*)h3, 2); // 1.3. Последний обработчик в цепочке - обработчик номер 1 ConcreteHandler1* h1 = new ConcreteHandler1((Handler*)h2, 1); // 2. Создать клиента. // Клиент указывает на цепочку объектов-обработчиков client -> h1-> h2 -> h3. Handler* client = new Handler((Handler*)h1); // 3. Ввести с клавиатуры номер обработчика int numObject; cout << "Enter object\'s number: "; cin >> numObject; // 4. В зависимости от numObject реализовать диспетчеризацию switch (numObject) { case 1: // Установить обработчика для клиента client->SetHandler((Handler*)h1, 1); // обработчик 1 client->HandleRequest(); // вызвать обработчика 1 break; case 2: // Установить обработчика для клиента client->SetHandler((Handler*)h2, 2); // обработчик 2 client->HandleRequest(); // вызвать обработчика 2 break; case 3: // Установить обработчика для клиента client->SetHandler((Handler*)h3, 3); // обработчик 3 client->HandleRequest(); // вызвать обработчика 3 break; default: cout << "Incorrect handler" << endl; break; } }
Результат выполнения программы
Enter object's number: 2 ConcreteHandler2.HandleRequest
⇑
Связанные темы
⇑