Patterns. The “Chain of Responsibility” pattern. Implementation in C++

The “Chain of Responsibility” pattern. General information. Implementation in C++


Contents


Search other resources:

1. General information about the pattern Chain of Responsibility

The Chain of Responsibility pattern refers to patterns of behavior for objects. The pattern is designed to create a chain of receiver objects (handlers) that receive and process some request from the client. The client (client object) sends a request that can be processed by one of the recipient objects. This request is treated sequentially until processing.

Characteristic features of the pattern:

  • the client object that initiates the request does not have information about which of the objects in the chain will process this request. The request has an implicit receiver;
  • there is no direct relationship between the sender and the receiver. The request from the sender is reviewed by the receiver objects along the chain until it is processed;
  • object-handler processes the request, depending on certain conditions. If the handler cannot process the request, it passes it to the next handler object;
  • if, during the viewing of the entire chain of handlers, the request has not been processed, then the last handler object in the chain can process this request in a special way.

The pattern has the following implementation features:

  • a hierarchy of classes is created containing links (pointers) to its successor;
  • a chain of handlers is built on the basis of the hierarchy. This is where handler objects are created that refer to their successor;
  • a client object is created that will send the request. This client object refers to the “closest” handler object. The term “closest” means that handler objects can refer to each other based on some class hierarchy. This hierarchy is built according to the principle “from the concrete to the general”;
  • the client object sends a request by calling the appropriate method (operation).

The interaction between client and handler objects can be summarized in the following steps:

  • create objects that process the request and form them into a chain;
  • create a client that points to the first object in the chain;
  • initiate a request by the client to view it by handler objects for processing. The request moves along the chain until some object processes it.

The Chain of Responsibility pattern is used in the following cases:

  • when there is more than one object that can process the request. This object is unknown in advance and should be found automatically;
  • when you need to send a request to one object from a set of objects, while not knowing which object will process the request;
  • when the set of objects that must process the request is generated dynamically.

 

2. Figure showing the structure of the Chain of Responsibility pattern. Classes required to implement the pattern

 Figure 1 shows the structure of the Chain of Responsibility pattern.

The structure of pattern Chain of Responsibility

Figure 1. The structure of pattern Chain of Responsibility

As you can see from Figure 1, to use the pattern, you need to implement the following classes:

  • the Handler class is a base class that implements an interface for processing requests. A class can implement communication with a successor;
  • classes ConcreteHandler1, ConcreteHandler2 …, ConcreteHandlerN which are concrete handlers. Each of these classes processes a request, for which it is responsible and has access to its successor. If the request cannot be processed, then it is passed on to the successor;
  • client class Client – sends a request to some object in the chain ConcreteHandler1, ConcreteHandler2 …, ConcreteHandlerN.

Figure 2 shows a typical structure of a chain of objects ConcreteHandler1, ConcreteHandler2 …, ConcreteHandlerN.

Pattern Chain of Responsibility. The structure of ConcreteHandler object

Figure 2. The structure of ConcreteHandler object and organization of the “chain”

 

3. Advantages and disadvantages

The Chain of Responsibility pattern has the following advantages:

  • the object handling the request does not know anything about the sender and the structure of the chain;
  • the client object sending the request does not know which recipient object will process the request;
  • simplified relationships between the object that sends the request, and objects that receive this request. The sender object only has a reference to the “closest” successor; it does not need to store references to other recipient objects;
  • the chain of responsibility provides flexibility in assigning responsibilities between entities. The pattern allows you to add new members to the chain and change their responsibilities.

The disadvantage of this pattern is that the request may not be processed at all. If the request, after going through the entire chain, reaches the last handler object and is not processed, then this request disappears. Such a case can arise in the case of incorrect construction of the chain structure.

 

4. An example of implementation of the “Chain of Responsibility” pattern in C++

This example implements the Chain of Responsibility pattern for the structure shown in Figure 3.

The structure of pattern Chain of Responsibility for 3 objects.

Figure 3. Structure of the Chain of Responsibility pattern implemented in C++

The program code that implements the Chain of Responsibility pattern for three specific handlers is as follows

#include <iostream>
using namespace std;

// Pattern Chain of Responsibility

// A class that serves as a uniform interface
// for all objects that process a request.
// This class specifies general methods for objects.
// Class defines:
// - the internal field successor - a pointer to successor;
// - the Handler() constructor;
// - method HandleRequest(), intended to process a request;
// - method DoRequest() - determines if the request needs to be executed;
// - method SetHandler() - sets the ability to be processed by a specific handler.
class Handler
{
private:
  Handler* successor; // pointer to follower
  int numHandler; // number of the handler that processes the request (1..3)

public:
  // Constructor
  Handler(Handler* _successor = nullptr, int numHandler = -1) :
    successor(_successor), numHandler(numHandler)
  { }

  // A virtual method for handling a request.
  virtual void HandleRequest()
  {
    if (successor)
      successor->HandleRequest();
  }

  // Method that determines whether to execute the request
  virtual bool DoRequest()
  {
    return numHandler != -1;
  }

  // The method that sets the handler and the ability to handle
  virtual void SetHandler(Handler* _successor, int _numHandler)
  {
    successor = _successor;
    numHandler = _numHandler;
  }
};

// Concrete handler - 1
class ConcreteHandler1 :Handler
{
public:
  // Constructor - takes 2 parameters:
  // - a pointer to the base class;
  // - a value that determines whether the request can be processed.
  ConcreteHandler1(Handler* handler, int numHandler = -1)
    : Handler(handler, numHandler)
  { }

  // Specific processing method
  virtual void HandleRequest()
  {
    // Checking if the given handler is processing the request.
    if (DoRequest()) // If the processes, then perform the appropriate actions
    {
      cout << "ConcreteHandler1.HandleRequest" << endl;
    }
    else
    {
      // if it does not process,
      // then pass the call to the next handler along the chain
      Handler::HandleRequest();
    }
  }
};

// Concrete handler - 2
class ConcreteHandler2 :Handler
{
public:
  // Constructor - takes 2 parameters:
  // - a pointer to the base class;
  // - a value that determines whether the request can be processed.
  ConcreteHandler2(Handler* handler, int numHandler = -1) :
    Handler(handler, numHandler)
  { }

  // Specific processing method
  virtual void HandleRequest()
  {
    // Checking if the given handler is processing the request.
    if (DoRequest()) // If it does, then take the appropriate action.
    {
      cout << "ConcreteHandler2.HandleRequest" << endl;
    }
    else
    {
      // if it does not process,
      // then pass the call to the next handler along the chain
      Handler::HandleRequest();
    }
  }
};

// The last concrete handler.
// This handler closes the processing.
// Therefore, it does not need to pass a reference to the previous handler.
class ConcreteHandler3 :Handler
{
public:
  // Constructor - takes 2 parameters:
  // - a pointer to the base class;
  // - a value that determines whether the request can be processed.
  ConcreteHandler3(int numHandler = -1) : Handler(nullptr, numHandler)
  { }

  // Specific processing method
  virtual void HandleRequest()
  {
    // Checking if the given handler is processing the request.
    if (DoRequest()) // If it does, then take the appropriate action.
    {
      cout << "ConcreteHandler3.HandleRequest" << endl;
    }
    else
    {
      // if it does not process, then display a message
      // stating that there is no corresponding handler in the chain.
      cout << "There is no corresponding handler in the chain." << endl;
    }
  }
};

void main()
{
  // Client code.
  // 1. Create a chain of three handlers, assign them the corresponding processing number
  // 1.1. The last handler in the chain is created first
  ConcreteHandler3* h3 = new ConcreteHandler3(3); // 3 - handler number in the chain

  // 1.2. Next number 2, gets a reference to the previous h3 handler
  ConcreteHandler2* h2 = new ConcreteHandler2((Handler*)h3, 2);

  // 1.3. The last handler in the chain is handler number 1
  ConcreteHandler1* h1 = new ConcreteHandler1((Handler*)h2, 1);

  // 2. Create a client.
  //   The client points to the chain of handler objects client -> h1-> h2 -> h3.
  Handler* client = new Handler((Handler*)h1);

  // 3. Enter the handler number from the keyboard
  int numObject;
  cout << "Enter object\'s number: ";
  cin >> numObject;

  // 4. Depending on numObject implement dispatching
  switch (numObject)
  {
    case 1:
      // Set the handler for the client
      client->SetHandler((Handler*)h1, 1); // handler 1
      client->HandleRequest(); // invoke the handler 1
      break;
    case 2:
      // Set the handler for the client
      client->SetHandler((Handler*)h2, 2); // handler 2
      client->HandleRequest(); // invoke the handler 2
      break;
    case 3:
      // Set the handler for the client
      client->SetHandler((Handler*)h3, 3); // handler 3
      client->HandleRequest(); // invoke the handler 3
      break;
    default:
      cout << "Incorrect handler" << endl;
      break;
  }
}

Program result

Enter object's number: 2
ConcreteHandler2.HandleRequest

 


Related topics