Pull to refresh

Паттерн проектирования «Цепочка обязанностей» / «Chain of Responsibility»

Perfect code *
Почитать описание других паттернов.


Проблема


Эффективно и компактно реализовать механизм обработки потока событий/запросов/сообщений в системах с потенциально большим количеством обработчиков.

Описание


Модель событие/обработчик широко применяется в программных системах из различных областей. В основном, это — графический интерфейс пользователя, где события, генерируемые от действий пользователя различным образом обрабатываются элементами интерфейса. Нельзя так-же забывать про WinAPI, который сплошь и рядом реализует такую модель. В большинстве источников эта модель имеет название Event Loop.


С одной стороны — все достаточно прозрачно. Есть множество типов сообщений, есть множество обработчиков этих сообщений. Задача — обрабатывать каждое сообщение в потоке ассоциированным с его конкретными типом обработчиком. Проще говоря, необходимо иметь эффективный механизм увязывания типов сообщений и обработчиков.

В большинстве случаев, с фиксированным и небольшим количеством обработчиков в системе, бывает достаточно реализовать данный механизм на базе конструкции if-then-else. Однако, не всегда все так просто. Во-первых, не всегда заранее известно сколько именно обработчиков необходимо. Во вторых — добавление в систему нового типа сообщения как правило приводит к появлению нового обработчика, внедрение которого уже становится действительно сложной задачей.

Для решения подобных проблем существует шаблон — “Цепочка обязанностей”.

Идея шаблона заключается в организации рекуррентного конвейера из обработчиков, в котором каждый обработчик может либо обработать поступившее сообщение (например, только сообщения определенного типа), либо делегировать обработку следующему в конвейере обработчику. Возможен еще вариант обработки и последующей передачи. При этом, клиенту, чтобы инициировать обработку того или иного сообщения достаточно лишь передать его первому в конвейере обработчику.

Практический пример


Рассмотрим, пожалуй самый наглядный пример шаблона — компьютерную сеть. Действительно — большое количество обработчиков — узлов сети (компьютеров, серверов, маршрутизаторов) и еще большое количество типов сетевых запросов.

Пусть в упрощенной и выдуманной сетевой модели есть 4-типа обработчиков — сеть, маршрутизатор, форвардер и сервер. Так-же есть всего один тип запроса — запрос на обработку сервером. Обработчики обладают следующим поведением: сеть — просто предает по своей среде запрос, маршрутизатор — передает запрос из одной сети в другую, форвардер — передает запрос конкретному хосту, сервер — обрабатывает запрос.

Узлы сети представляют собой конвейер обработчиков. Запрос — конкретное сообщение. Запрос, двигаясь по цепочке обрабатывается (маршрутизируется, форвардится) каждым ее узлом и передается дальше. До тех пор, пока не будет окончательно обработан сервером.

Диаграмма классов


Основной момент на который следует обратить внимание — способ организации конвейерной обработки. В данном случае используется следующий подход. Все обработчики реализуют один абстрактный класс — RequestHandled, который содержит ссылку на самого себя (successor) для делегирования обязанностей по обработке следующему обработчику в конвейере. Реализация метода handleRequest() по-умолчанию реализует такую делегацию.


Реализация на С++


// Запрос
#ifndef REQUEST_H
#define REQUEST_H

#include <string>

using namespace std;

class Request {
private:
protected:
public:
  string requestString;
  Request(string requestString) : requestString(requestString) { }
};
#endif

// Обработчик
#ifndef REQUEST_HANDLER_H
#define REQUEST_HANDLER_H

#include "Request.h"

class RequestHandler {
protected:
  RequestHandler *successor;
public:
  RequestHandler();
  RequestHandler(RequestHandler *successor) : successor(successor) { } 

  virtual void handleRequest(const Request& request) {
    successor->handleRequest(request);
  }
};

RequestHandler::RequestHandler() { }

#endif

// Хост
#ifndef HOST_H
#define HOST_H

#include "RequestHandler.h"

class Host : public RequestHandler {
private:
protected:
public:
  Host();
  Host(Host *host);
};

Host::Host() { }
Host::Host(Host *host) : RequestHandler((RequestHandler*)host) { }

#endif

// Сеть
#ifndef NETWORK_H
#define NETWORK_H

#include "RequestHandler.h"
#include "Router.h"

class Network : public RequestHandler {
private:
protected:
public:
  Network(Host *host);
};

Network::Network(Host *host) : RequestHandler((RequestHandler*)host) { }
#endif

// Роутер
#ifndef ROUTER_H
#define ROUTER_H

#include "Host.h"
#include "Network.h"
#include "RequestHandler.h"
#include "Request.h"

#include <cstdlib>
#include <iostream>

using namespace std;

class Router : public Host {
private:
  void route(Network *network, const Request& request) {
    if (network != NULL) {
      ((RequestHandler*)network)->handleRequest(request);
    } else {
      cout << "ER: Network is unreachable. Request with string " << request.requestString << " was lost" << endl;
    }
  }
protected:
public:

  Router(Network *network);
  virtual void handleRequest(const Request& request) {
    route((Network*) successor, request);
  }
};

Router::Router(Network *network) {
  successor = (RequestHandler*) network;
}

#endif

// Сервер
#ifndef SERVER_H
#define SERVER_H

#include "Host.h"
#include "Request.h"

#include "string"

using namespace std;

class Server : public Host {
private:
  void showMessage(const string& msg) {
    cout << msg << endl;
  }
protected:
public:
  Server() : Host(NULL) {
    
  }

  virtual ~Server();

  virtual void handleRequest(const Request& request) {
    string messageStr = "Request received with string: " + request.requestString;
    showMessage(messageStr);
  }
};

#endif

// Форвардер
#ifndef FORWARDER_H
#define FORWARDER_H

#include "Host.h"
#include "Request.h"

class Forwarder : public Host {
private:
protected:
public:

  Forwarder(Host *host) : Host(host) { } 
  virtual ~Forwarder();
};

#endif

// Клиент
#include <iostream>
#include <cstdlib>

using namespace std;

#include "Server.h"
#include "Router.h"
#include "Forwarder.h"
#include "Network.h"

int main(int argc, char *argv[]) {

  Server *webServer = new Server();
  Forwarder *fw1 = new Forwarder(webServer);
  Forwarder *fw2 = new Forwarder(fw1);
  Network *network = new Network(fw2);
  Router *router = new Router(network);

  const Request *req = new Request("correct request");
  router->handleRequest(*req);

  Router *router1 = new Router(NULL);
  Forwarder *fw3 = new Forwarder(router1);
  Forwarder *fw4 = new Forwarder(fw3);
  Network *network2 = new Network(fw4);
  Router *router2 = new Router(network2);

  const Request *req1 = new Request("incorrect request");
  router2->handleRequest(*req1);

  system("pause");
}


PS


Внимательный читатель скажет — “О! Я уже читал статьи этого парня!”. Все верно, я решил продолжить серию статей о шаблонах проектирования и надеюсь на ваши фидбеки.
Tags:
Hubs:
Total votes 51: ↑45 and ↓6 +39
Views 33K
Comments Comments 24