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 (цепочка обязанностей). Структура объектов

Рисунок 2. Структура объектов ConcreteHandler и организация «цепочки»

 

3. Преимущества и недостатки

Паттерн «цепочка обязанностей» имеет следующие преимущества:

  • объект, обрабатывающий запрос, не знает ничего об отправителе и структуре цепочки;
  • объект-клиент, посылающий запрос, не знает какой объект-получатель обработает запрос;
  • упрощаются взаимосвязи между объектом который отправляет запрос и объектами, получающими этот запрос. Объект-отправитель имеет ссылку только на «ближайшего» преемника, ему не нужно хранить ссылки на другие объекты-получатели;
  • цепочка обязанностей обеспечивает гибкость при распределении обязанностей между объектами. Паттерн позволяет добавлять новых участников в цепочку и менять их обязанности.

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

 

4. Пример реализации паттерна «Цепочка обязанностей» на языке C++

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

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

  // 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

 


Связанные темы