Поиск по сайту

 

Паттерны » Паттерны поведения » Chain of Responsibility

Паттерн Chain of Responsibility (цепочка обязанностей)

Назначение паттерна Chain of Responsibility

  • Паттерн Chain of Responsibility позволяет избежать жесткой зависимости отправителя запроса от его получателя, при этом запрос может быть обработан несколькими объектами. Объекты-получатели связываются в цепочку. Запрос передается по этой цепочке, пока не будет обработан.
  • Вводит конвейерную обработку для запроса с множеством возможных обработчиков.
  • Объектно-ориентированный связанный список с рекурсивным обходом.

Решаемая проблема

Имеется поток запросов и переменное число "обработчиков" этих запросов. Необходимо эффективно обрабатывать запросы без жесткой привязки к их обработчикам, при этом запрос может быть обработан любым обработчиком.

Паттерн Сhain of Responsibility. Обработка запроса клиента

Обсуждение паттерна Chain of Responsibility

Инкапсулирует элементы по обработке запросов внутри абстрактного "конвейера". Клиенты "кидают" свои запросы на вход этого конвейера.

Паттерн Chain of Responsibility. Конвейер обработки запроса клиента

Паттерн Chain of Responsibility связывает в цепочку объекты-получатели, а затем передает запрос-сообщение от одного объекта к другому до тех пор, пока не достигнет объекта, способного его обработать. Число и типы объектов-обработчиков заранее неизвестны, они могут настраиваться динамически. Механизм связывания в цепочку использует рекурсивную композицию, что позволяет использовать неограниченное число обработчиков.

Паттерн Chain of Responsibility упрощает взаимосвязи между объектами. Вместо хранения ссылок на всех кандидатов-получателей запроса, каждый отправитель хранит единственную ссылку на начало цепочки, а каждый получатель имеет единственную ссылку на своего преемника - последующий элемент в цепочке.

Убедитесь, что система корректно "отлавливает" случаи необработанных запросов.

Не используйте паттерн Chain of Responsibility, когда каждый запрос обрабатывается только одним обработчиком, или когда клиент знает, какой именно объект должен обработать его запрос.

Структура паттерна Chain of Responsibility

Производные классы знают, как обрабатывать запросы клиентов. Если "текущий" объект не может обработать запрос, то он делегирует его базовому классу, который делегирует "следующему" объекту и так далее.

UML-диаграмма классов паттерна Chain of Responsibility

UML-диаграмма классов паттерна Chain of Responsibility

Обработчики могут вносить свой вклад в обработку каждого запроса. Запрос может быть передан по всей длине цепочки до самого последнего звена.

Пример паттерна Chain of Responsibility

Паттерн Chain of Responsibility позволяет избежать привязки отправителя запроса к его получателю, давая шанс обработать запрос нескольким получателям. Банкомат использует Chain of Responsibility в механизме выдачи денег.

Пример паттерна Chain of Responsibility

Использование паттерна Chain of Responsibility

  • Базовый класс имеет указатель на "следующий обработчик".
  • Каждый производный класс реализует свой вклад в обработку запроса.
  • Если запрос должен быть "передан дальше", то производный класс "вызывает" базовый класс, который с помощью указателя делегирует запрос далее.
  • Клиент (или третья сторона) создает цепочку получателей (которая может иметь ссылку с последнего узла на корневой узел).
  • Клиент передает каждый запрос в начало цепочки.
  • Рекурсивное делегирование создает иллюзию волшебства.

Особенности паттерна Chain of Responsibility

  • Паттерны Chain of Responsibility, Command, Mediator и Observer показывают, как можно разделить отправителей и получателей с учетом их особенностей. Chain of Responsibility передает запрос отправителя по цепочке потенциальных получателей.
  • Chain of Responsibility может использовать Command для представления запросов в виде объектов.
  • Chain of Responsibility часто применяется вместе с паттерном Composite. Родитель компонента может выступать в качестве его преемника.

Реализация паттерна Chain of Responsibility

Реализация паттерна Chain of Responsibility по шагам

  1. Создайте указатель на следующий обработчик next в базовом классе.
  2. Метод handle() базового класса всегда делегирует запрос следующему объекту.
  3. Если производные классы не могут обработать запрос, они делегируют его базовому классу.
#include <iostream>
#include <vector>
#include <ctime>
using namespace std;

class Base
{
    // 1. Указатель "next" в базовом классе
    Base *next;
  public:
    Base()
    {
        next = 0;
    }
    void setNext(Base *n)
    {
        next = n;
    }
    void add(Base *n)
    {
        if (next)
          next->add(n);
        else
          next = n;
    }
    // 2. Метод базового класса, делегирующий запрос next-объекту
    virtual void handle(int i)
    {
        next->handle(i);
    }
};

class Handler1: public Base
{
  public:
     /*virtual*/void handle(int i)
    {
        if (rand() % 3)
        {
            // 3. 3 из 4 запросов не обрабатываем
            cout << "H1 passsed " << i << "  ";
            // 3. и делегируем базовому классу
            Base::handle(i);        }
        else
          cout << "H1 handled " << i << "  ";
    }
};

class Handler2: public Base
{
  public:
     /*virtual*/void handle(int i)
    {
        if (rand() % 3)
        {
            cout << "H2 passsed " << i << "  ";
            Base::handle(i);
        }
        else
          cout << "H2 handled " << i << "  ";
    }
};

class Handler3: public Base
{
  public:
     /*virtual*/void handle(int i)
    {
        if (rand() % 3)
        {
            cout << "H3 passsed " << i << "  ";
            Base::handle(i);
        }
        else
          cout << "H3 handled " << i << "  ";
    }
};

int main()
{
  srand(time(0));
  Handler1 root;
  Handler2 two;
  Handler3 thr;
  root.add(&two);
  root.add(&thr);
  thr.setNext(&root);
  for (int i = 1; i < 10; i++)
  {
    root.handle(i);
    cout << '\n';
  }
}

Вывод программы:

H1 passsed 1  H2 passsed 1  H3 passsed 1  H1 passsed 1  H2 handled 1
H1 handled 2
H1 handled 3
H1 passsed 4  H2 passsed 4  H3 handled 4
H1 passsed 5  H2 handled 5
H1 passsed 6  H2 passsed 6  H3 passsed 6  H1 handled 6
H1 passsed 7  H2 passsed 7  H3 passsed 7  H1 passsed 7  H2 handled 7
H1 handled 8	
H1 passsed 9  H2 passsed 9  H3 handled 9

Реализация паттерна Chain of Responsibility: Chain and Composite

  1. Создайте указатель на следующий обработчик next в базовом классе.
  2. Метод handle() базового класса всегда делегирует запрос следующему объекту.
  3. Если производные классы не могут обработать запрос, они делегируют его базовому классу.
#include <iostream>
#include <vector>
#include <ctime>
using namespace std;

class Component
{
    int value;
    // 1. Указатель "next" в базовом классе
    Component *next; 
  public:
    Component(int v, Component *n)
    {
        value = v;
        next = n;
    }
    void setNext(Component *n)
    {
        next = n;
    }
    virtual void traverse()
    {
        cout << value << ' ';
    }
    // 2. Метод базового класса, делегирующий запрос next-объекту
    virtual void volunteer()
    {
        next->volunteer();
    }
};

class Primitive: public Component
{
  public:
    Primitive(int val, Component *n = 0): Component(val, n){}
     /*virtual*/void volunteer()
    {
        Component::traverse();
        // 3. Примитивные объекты не обрабатывают 5 из 6 запросов
        if (rand() *100 % 6 != 0)
          // 3. Делегируем запрос в базовый класс
          Component::volunteer();
    }
};

class Composite: public Component
{
    vector < Component * > children;
  public:
    Composite(int val, Component *n = 0): Component(val, n){}
    void add(Component *c)
    {
        children.push_back(c);
    }
     /*virtual*/void traverse()
    {
        Component::traverse();
        for (int i = 0; i < children.size(); i++)
          children[i]->traverse();
    }
    // 3. Составные объекты никогда не обрабатывают запросы
     /*virtual*/void volunteer()
    {
        Component::volunteer();
    }
};

int main()
{
  srand(time(0));                 // 1
  Primitive seven(7);             // |
  Primitive six(6, &seven);       // +-- 2
  Composite three(3, &six);       // |   |
  three.add(&six);
  three.add(&seven);              // |   +-- 4 5
  Primitive five(5, &three);      // |
  Primitive four(4, &five);       // +-- 3
  Composite two(2, &four);        // |   |
  two.add(&four);
  two.add(&five);                 // |   +-- 6 7
  Composite one(1, &two);         // |
  Primitive nine(9, &one);        // +-- 8 9
  Primitive eight(8, &nine);
  one.add(&two);
  one.add(&three);
  one.add(&eight);
  one.add(&nine);
  seven.setNext(&eight);
  cout << "traverse: ";
  one.traverse();
  cout << '\n';
  for (int i = 0; i < 8; i++)
  {
    one.volunteer();
    cout << '\n';
  }
}

Вывод программы:

traverse: 1 2 4 5 3 6 7 8 9
4
4 5 6 7
4 5 6 7 8 9 4 5 6 7 8 9 4
4
4 5 6
4 5
4 5
4 5 6 7 8 9 4 5 6 7 8 9 4 5 6

Источник: http://sourcemaking.com/design_patterns/chain_of_responsibility/