Patterns. Паттерн Chain of Responsibility (ланцюжок обов’язків). Реалізація на C++

Паттерн Chain of Responsibility (ланцюжок обов’язків). Загальні відомості. Реалізація на C++


Зміст


Пошук на інших ресурсах:

1. Загальні відомості про паттерн Chain of Responsibility (ланцюжок обов’язків)

Паттерн Chain of Responsibility (ланцюжок обов’язків) відноситься до паттернів поведінки об’єктів. Паттерн призначений для створення ланцюжка об’єктів-отримувачів (обробників), які отримують та обробляють деякий запит від клієнта. Клієнт (клієнтський об’єкт) посилає запит, який може бути оброблений одним з об’єктів-отримувачів. Цей запит розглядається послідовно до його обробки.

Характерні риси паттерну:

  • об’єкт-клієнт, що ініціює запит, не володіє інформацією про те, який з об’єктів у ланцюгу буде цей запит обробляти. У запиту є анонімний отримувач (implicit receiver);
  • немає прямої залежності між відправником та приймачем. Запит від відправника переглядається об’єктами-приймачами по ланцюжку до тих пір, поки не буде оброблений;
  • об’єкт-обробник обробляє запит в залежності від виконання деякої умови. Якщо обробник не може обробити запит, він передає його наступному об’єкту-обробнику;
  • якщо, протягом перегляду всього ланцюга обробників, запит не був оброблений, то останній об’єкт обробник в ланцюгу може по особливому обробити цей запит.

Паттерн має наступні особливості реалізації:

  • створюється ієрархія класів, що містять посилання (покажчик) на свого спадкоємця (successor);
  • на основі ієрархії будується ланцюжок обробників. Тут створюються об’єкти-обробники, що мають посилання на свого наступника (successor). ;
  • створюється об’єкт-клієнт, що буде надсилати запит. Цей об’єкт-клієнт посилається на “найближчий” об’єкт-обробник. Термін “найближчий” означає, що об’єкти-обробники можуть посилатись один на одного з врахуванням деякої ієрархії класів. Ця ієрархія будується за принципом “від конкретного до загального”;
  • об’єкт-клієнт надсилає запит шляхом виклику відповідного методу (операції).

Взаємодію між клієнтом та об’єктами-обробниками можна сформулювати наступними кроками:

  • створити об’єкти, які обробляють запит та сформувати їх в ланцюг;
  • створити клієнта, який вказує на перший об’єкт в ланцюгу;
  • ініціювати запит клієнтом для його перегляду об’єктами-обробниками на питання обробки. Запит просувається по ланцюжку поки деякий об’єкт не обробить його.

Паттерн Chain of Responsibility використовується у наступних випадках:

  • коли існує більше ніж один об’єкт, здатний опрацювати запит. Цей об’єкт наперед невідомий та має бути знайдений автоматично;
  • коли потрібно відправити запит одному об’єкту з набору об’єктів, при цьому не знаючи, який саме об’єкт опрацює запит;
  • коли набір об’єктів, які мають опрацювати запит, формується динамічно.

 

2. Рисунок, що демонструє структуру паттерну Chain of Responsibility. Класи, що необхідні для реалізацїі паттерну

На рисунку 1 зображена структура паттерну Chain of Responsibility.

Паттерни. Структура паттерну Chain of Responsibility (ланцюжок обов'язків)

Рисунок 1. Структура паттерну Chain of Responsibility

Як видно з рисунку 1, для використання паттерну потрібно реалізувати наступні класи:

  • клас Handler – базовий клас, що реалізує інтерфейс для обробки запитів. Клас може реалізовувати зв’язок з послідовником;
  • класи ConcreteHandler1, ConcreteHandler2, …, ConcreteHandlerN що є конкретними обробниками. Кожен з цих класів обробляє запит, за який відповідає та має доступ до свого спадкоємця. Якщо запит неможливо обробити, то відбувається його передача спадкоємцю;
  • клієнтський клас Client – відправляє запит деякому об’єкту в ланцюжку ConcreteHandler1, ConcreteHandler2, …, ConcreteHandlerN.

На рисунку 2 зображена типова структура ланцюга об’єктів ConcreteHandler1, ConcreteHandler2, …, ConcreteHandlerN.

Паттерн Chain of Responsibility (ланцюжок обов'язків). Структура об’єктів ConcreteHandler та організація "ланцюжка"

Рисунок 2. Структура об’єктів ConcreteHandler та організація “ланцюжка”

 

3. Переваги та недоліки

Паттерн “ланцюжок обов’язків” має наступні переваги:

  • об’єкт, що обробляє запит, не знає нічого про відправника та структуру ланцюжка;
  • об’єкт-клієнт, що посилає запит, не знає який об’єкт-отримувач опрацює запит;
  • спрощуються взаємозв’язки між об’єктом який відправляє запит та об’єктами, що отримують цей запит. Об’єкт-відправник має посилання тільки на “найближчого” наступника, йому не потрібно зберігати посилання на інші об’єкти-отримувачі;
  • ланцюжок обов’язків забезпечує гнучкість при розподілі обов’язків між об’єктами. Паттерн дозволяє додавати нових учасників у ланцюжок та змінювати їх обов’язки.

Недоліком даного паттерну є те, що запит може бути взагалі не оброблений. Якщо запит, пройшовши весь ланцюжок, досягає останнього об’єкта-обробника і не опрацьовується, то цей запит зникає. Такий випадок може виникнути у випадку неправильної побудови структури ланцюжка.

 

4. Приклад реалізації паттерну “Ланцюжок обов’язків” на мові C++

У даному прикладі реалізовано паттерн Chain of Responsibility для структури зображеної на рисунку 3.

Паттерни. Структура паттерну 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);

  // 4. Ввести з клавіатури номер обробника
  int numObject;
  cout << "Enter object\'s number: ";
  cin >> numObject;

  // 5. В залежності від 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

 


Споріднені теми