Паттерн 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 для трьох обробників, реалізованого на 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
⇑
Споріднені теми
⇑