10.8. Chain of Responsibility pattern

Sometimes we need to give more than one object a chance to handle a request. The Chain of Responsibility pattern is a 'behavioral' software design pattern. The goal of the pattern is to separate request senders and request handlers, while giving more than one object a chance to handle the request.

The Chain of Responsibility design pattern allows an object to send a command without knowing what object will receive and handle it. The request is sent from one object to another making them parts of a chain and each object in this chain can handle the command, pass it on or do both. The most usual example of a machine using the Chain of Responsibility is the vending machine coin slot: rather than having a slot for each type of coin, the machine has only one slot for all of them. The dropped coin is routed to the appropriate storage place that is determined by the receiver of the command.

Instead of calling a single function to satisfy a request, multiple functions in the chain have a chance to satisfy the request. Since the chain is effectively a list, it can be dynamically created, so you could also think of it as a more general, dynamically-built switch statement.

        classDiagram
   direction TB
   client ..> food_handlers : uses
   food_handlers --|> food_handler
   food_handler <|-- monkey_food_handler
   food_handler <|-- squirrel_food_handler
   food_handler <|-- dog_food_handler
   food_handler --* food_handlers : chain

   class food_handler {
     <<interface>>
     +handle(request string_view) string
   }
   class monkey_food_handler {
     +handle(request string_view) string
   }
   class squirrel_food_handler {
     +handle(request string_view) string
   }
   class dog_food_handler {
     +handle(request string_view) string
   }
   class food_handlers {
     -chain_ vector~unique_ptr~food_handler~~
     +handle(food_item string_view) string
   }
   class client {
     <<function>>
   }
    

Food handler chain of responsibility

Interface

The interface declares methods for building the chain of handlers and for executing a request.

struct food_handler {
  virtual std::string handle(std::string_view request) const = 0;
  virtual ~food_handler() = default;
};

Note this class has no logic to visit the next node. This class has one job - provide an interface to handle a request.

Implementing classes

The implementing classes are where 'handling' gets done.

Classes return an empty string if they do not handle anything.

The string is used as an early exit condition from the chain: as soon as some link in the change returns a non-empty string the chain can return the result to the client.

struct monkey_food_handler : food_handler {
  std::string handle(std::string_view request) const override {
    if (request == "Banana") {
      return "Monkey: I'll eat the " + std::string{request} + ".\n";
    }
    return {};
  }
};

struct squirrel_food_handler : food_handler {
  std::string handle(std::string_view request) const override {
    if (request == "Nut") {
      return "Squirrel: I'll eat the " + std::string{request} + ".\n";
    }
    return {};
  }
};

struct dog_food_handler : food_handler {
  std::string handle(std::string_view request) const override {
    if (request == "MeatBall") {
      return "Dog: I'll eat the " + std::string{request} + ".\n";
    }
    return {};
  }
};

In this example we use a vector to manage the handlers. Any iterable standard library container could be used here, but vector, array, and list would be typical choices.

The constructor builds the chain, which is completely private.

class food_handlers : public food_handler {
public:
  food_handlers() {
    chain_.push_back(std::make_unique<monkey_food_handler>());
    chain_.push_back(std::make_unique<squirrel_food_handler>());
    chain_.push_back(std::make_unique<dog_food_handler>());
  }

  std::string handle(std::string_view food_item) const override {
    for (const auto& link: chain_) {
      if (std::string reply = link->handle(food_item); !reply.empty()) {
        return reply;
      }
    }
    return {};
  }

private:
  std::vector<std::unique_ptr<food_handler>> chain_;
};

No other code needs to know what classes are actually in the chain.

Client

The client function provides the data that needs to be handled - in this case our food items. The client also needs to know about food handlers as a group, but does not know what classes are doing the handling or how many there are.

void client(const food_handlers& eaters) {
  const std::vector<std::string_view> food {"Nut", "Banana", "Cup of coffee"};

  for (const auto& snack : food) {
    std::cout << "Client: Who wants a " << snack << "?\n";
    const std::string result = eaters.handle(snack);
    if (result.empty()) {
      std::cout << ' ' << snack << " was left untouched.\n";
    } else {
      std::cout << ' ' << result;
    }
  }
}

One of the big benefits of all this work is how little main needs to know:

int main() {
  food_handlers eaters;
  client(eaters);
}

Run It

 1#include <iostream>
 2#include <memory>
 3#include <string>
 4#include <string_view>
 5#include <vector>
 6
 7struct food_handler {
 8  virtual std::string handle(std::string_view request) const = 0;
 9  virtual ~food_handler() = default;
10};
11
12struct monkey_food_handler : food_handler {
13  std::string handle(std::string_view request) const override {
14    if (request == "Banana") {
15      return "Monkey: I'll eat the " + std::string{request} + ".\n";
16    }
17    return {};
18  }
19};
20
21struct squirrel_food_handler : food_handler {
22  std::string handle(std::string_view request) const override {
23    if (request == "Nut") {
24      return "Squirrel: I'll eat the " + std::string{request} + ".\n";
25    }
26    return {};
27  }
28};
29
30struct dog_food_handler : food_handler {
31  std::string handle(std::string_view request) const override {
32    if (request == "MeatBall") {
33      return "Dog: I'll eat the " + std::string{request} + ".\n";
34    }
35    return {};
36  }
37};
38
39class food_handlers : public food_handler {
40public:
41  food_handlers() {
42    chain_.push_back(std::make_unique<monkey_food_handler>());
43    chain_.push_back(std::make_unique<squirrel_food_handler>());
44    chain_.push_back(std::make_unique<dog_food_handler>());
45  }
46
47  std::string handle(std::string_view food_item) const override {
48    for (const auto& link: chain_) {
49      if (std::string reply = link->handle(food_item); !reply.empty()) {
50        return reply;
51      }
52    }
53    return {};
54  }
55
56private:
57  std::vector<std::unique_ptr<food_handler>> chain_;
58};
59
60void client(const food_handlers& eaters) {
61  const std::vector<std::string_view> food {"Nut", "Banana", "Cup of coffee"};
62
63  for (const auto& item : food) {
64    std::cout << "Client: Who wants a " << item << "?\n";
65    const std::string result = eaters.handle(item);
66    if (result.empty()) {
67      std::cout << ' ' << item << " was left untouched.\n";
68    } else {
69      std::cout << ' ' << result;
70    }
71  }
72}
73
74int main() {
75  food_handlers eaters;
76  client(eaters);
77}

This next fun example is adapted from Thinking in C++, Vol 2. It uses some non-standard vocabulary to define the basic elements of the chain, but it is still a chain of responsibility.

Interface

First we define an interface each handler in the chain of responsibility must implement.

#include <iostream>
#include <memory>
#include <vector>

enum class Answer { NO, YES };

// This is our handler interface.
// Every class that inherits from this
// must implement the canIHave function
struct GimmeStrategy {
  virtual Answer canIHave() = 0;
  virtual ~GimmeStrategy() = default;
};

Rather than a bool, in this case, our early termination criteria is an enumerated type.

Implementing classes

For a chain to be a chain, at least two classes must implement the interface. (It's not much of a chain with only 1 link).

struct AskMom : public GimmeStrategy {
  Answer canIHave() {
    std::cout << "Mommy? Can I have this?\n";
    return Answer::NO;
  }
};

struct AskDad : public GimmeStrategy {
  Answer canIHave() {
    std::cout << "Dad, I really need this!\n";
    return Answer::NO;
  }
};

struct AskGrandpa : public GimmeStrategy {
  Answer canIHave() {
    std::cout << "Grandpa, is it my birthday yet?\n";
    return Answer::NO;
  }
};

struct AskGrandma : public GimmeStrategy {
  Answer canIHave() {
    std::cout << "Grandma, I really love you!\n";
    return Answer::YES;
  }
};

Building the chain

Much discussion related to this pattern is about how to create the chain of responsibility as a linked list. However, when you look at the pattern it really shouldn't matter how the chain is created: that's an implementation detail. The only important part is that some kind of iterable type is used to visit each handler. How that is implemented should be invisible to users.

While the Gimme class also is derived from the GimmeStrategy it is used to construct the chain of all the other strategies used.

class Gimme : public GimmeStrategy {
   private:
      std::vector<std::unique_ptr<GimmeStrategy>> chain;
   public:
     Gimme() {
       chain.push_back(std::unique_ptr<GimmeStrategy>(new AskMom));
       chain.push_back(std::unique_ptr<GimmeStrategy>(new AskDad));
       chain.push_back(std::unique_ptr<GimmeStrategy>(new AskGrandpa));
       chain.push_back(std::unique_ptr<GimmeStrategy>(new AskGrandma));
     }
     Answer canIHave() {
       for (const auto& it: chain) {
         if (it->canIHave() == Answer::YES) {
            return Answer::YES;
         }
       }
       // Reached end without success...
       std::cout << "Waaaaaahh!!\n";
       return Answer::NO;
     }
};

Run It

Once the abstract and implementing classes have been defined, then calling the chain is easy:

int main() {
  Gimme chain;
  chain.canIHave();
}
 1#include <iostream>
 2#include <memory>
 3#include <vector>
 4
 5enum class Answer { NO, YES };
 6
 7struct GimmeStrategy {
 8  virtual Answer canIHave() = 0;
 9  virtual ~GimmeStrategy() = default;
10};
11
12struct AskMom : public GimmeStrategy {
13  Answer canIHave() {
14    std::cout << "Mommy? Can I have this?\n";
15    return Answer::NO;
16  }
17};
18
19struct AskDad : public GimmeStrategy {
20  Answer canIHave() {
21    std::cout << "Dad, I really need this!\n";
22    return Answer::NO;
23  }
24};
25
26struct AskGrandpa : public GimmeStrategy {
27  Answer canIHave() {
28    std::cout << "Grandpa, is it my birthday yet?\n";
29    return Answer::NO;
30  }
31};
32
33struct AskGrandma : public GimmeStrategy {
34  Answer canIHave() {
35    std::cout << "Grandma, I really love you!\n";
36    return Answer::YES;
37  }
38};
39
40class Gimme : public GimmeStrategy {
41   private:
42      std::vector<std::unique_ptr<GimmeStrategy>> chain;
43   public:
44     Gimme() {
45       chain.push_back(std::unique_ptr<GimmeStrategy>(new AskMom));
46       chain.push_back(std::unique_ptr<GimmeStrategy>(new AskDad));
47       chain.push_back(std::unique_ptr<GimmeStrategy>(new AskGrandpa));
48       chain.push_back(std::unique_ptr<GimmeStrategy>(new AskGrandma));
49     }
50     Answer canIHave() {
51       for (const auto& it: chain) {
52         if (it->canIHave() == Answer::YES) {
53            return Answer::YES;
54         }
55       }
56       // Reached end without success...
57       std::cout << "Waaaaaahh!!\n";
58       return Answer::NO;
59     }
60};
61
62int main() {
63  Gimme chain;
64  chain.canIHave();
65}

10.8.1. The 'classic' version

The 'classic' implementation of the chain of responsibility implements the design shown in the UML diagram with a handler interface and an abstract handler to encapsulate maintenance of the chain.

        classDiagram
   client ..> handler
   handler --> handler : next

   handler <|-- receiver1
   handler <|-- receiver2
   handler <|-- receiver3
   class handler {
      <<interface>>
      +handle()
   }
   class receiver1 {
      +handle() override
   }
   class receiver2 {
      +handle() override
   }
   class receiver3 {
      +handle() override
   }
    

The 'classic' Chain of Responsibility in the 'Gang of Four' Patterns book does some work that is not needed in C++. Specifically the code related to managing a linked list from scratch. The standard library provides several containers that will work just as well. This is one of the reasons why this pattern is no longer implemented as written in the original book.

Keeping in mind that the essence of Chain of Responsibility is to try a number of solutions until you find one that works, you'll realize that the implementation of the sequencing mechanism is not an essential part of the pattern.

Interface

The Handler interface declares methods for building the chain of handlers and for executing a request.

struct handler {
    virtual handler* next(handler* handler) = 0;
    virtual std::string handle(std::string request) = 0;
    virtual ~handler() = default;
};

The AbstractHandler manages the chain maintenance common to all handlers.

class abstract_handler : public handler {
  private:
    handler* next_handler_ = nullptr;

  public:
    abstract_handler() : next_handler_(nullptr) {
    }
    handler* next(handler* link) override {
      next_handler_ = link;
      return link;
    }
    std::string handle(std::string request) override {
      if (next_handler_ != nullptr) {
        return this->next_handler_->handle(request);
      }
      return {};
    }
};

Implementing classes

All Concrete Handlers either handle a request or pass it to the next handler in the chain.

struct monkey_handler : public abstract_handler {
    std::string handle(std::string request) override {
      if (request == "Banana") {
        return "Monkey: I'll eat the " + request + ".\n";
      } else {
        return abstract_handler::handle(request);
      }
    }
};

struct squirrel_handler : public abstract_handler {
    std::string handle(std::string request) override {
      if (request == "Nut") {
        return "Squirrel: I'll eat the " + request + ".\n";
      } else {
        return abstract_handler::handle(request);
      }
    }
};

struct dog_handler : public abstract_handler {
    std::string handle(std::string request) override {
      if (request == "Meatball") {
        return "Dog: I'll eat the " + request + ".\n";
      } else {
        return abstract_handler::handle(request);
      }
    }
};

Client

A client uses the handler to process its data. The client passes each item to be process to the handler one at a time, but it unaware that anything other than the handler is involved.

void client(handler& food_handler) {
  std::vector<std::string> food = {"Nut", "Banana", "Cup of coffee"};
  for (const std::string &snack : food) {
    std::cout << "Client: Who wants a " << snack << "?\n";
    const std::string result = food_handler.handle(snack);
    if (!result.empty()) {
      std::cout << "  " << result;
    } else {
      std::cout << "  " << snack << " was left untouched.\n";
    }
  }
}

int main() {
  auto monkey = new monkey_handler;
  auto squirrel = new squirrel_handler;
  auto dog = new dog_handler;
  monkey->next(squirrel)->next(dog);

  client(* monkey);

  delete monkey;
  delete squirrel;
  delete dog;

  return 0;

}

However, the linked list of handlers needs to be manually constructed somewhere before it can be used. This gives users flexibility in what is included in the list, but requires knowledge of the internal workings of the chain which are better kept private.

And the memory is the responsibility of users to clean up.

Run It

 1#include <iostream>
 2#include <string>
 3#include <vector>
 4
 5struct handler {
 6    virtual handler* next(handler* handler) = 0;
 7    virtual std::string handle(std::string request) = 0;
 8    virtual ~handler() = default;
 9};
10
11class abstract_handler : public handler {
12  private:
13    handler* next_handler_ = nullptr;
14
15  public:
16    abstract_handler() : next_handler_(nullptr) {
17    }
18    handler* next(handler* link) override {
19      next_handler_ = link;
20      return link;
21    }
22    std::string handle(std::string request) override {
23      if (next_handler_ != nullptr) {
24        return this->next_handler_->handle(request);
25      }
26      return {};
27    }
28};
29
30struct monkey_handler : public abstract_handler {
31    std::string handle(std::string request) override {
32      if (request == "Banana") {
33        return "Monkey: I'll eat the " + request + ".\n";
34      } else {
35        return abstract_handler::handle(request);
36      }
37    }
38};
39
40struct squirrel_handler : public abstract_handler {
41    std::string handle(std::string request) override {
42      if (request == "Nut") {
43        return "Squirrel: I'll eat the " + request + ".\n";
44      } else {
45        return abstract_handler::handle(request);
46      }
47    }
48};
49
50struct dog_handler : public abstract_handler {
51    std::string handle(std::string request) override {
52      if (request == "Meatball") {
53        return "Dog: I'll eat the " + request + ".\n";
54      } else {
55        return abstract_handler::handle(request);
56      }
57    }
58};
59
60
61void client(handler& food_handler) {
62  std::vector<std::string> food = {"Nut", "Banana", "Cup of coffee"};
63  for (const std::string &snack : food) {
64    std::cout << "Client: Who wants a " << snack << "?\n";
65    const std::string result = food_handler.handle(snack);
66    if (!result.empty()) {
67      std::cout << "  " << result;
68    } else {
69      std::cout << "  " << snack << " was left untouched.\n";
70    }
71  }
72}
73
74int main() {
75  auto monkey = new monkey_handler;
76  auto squirrel = new squirrel_handler;
77  auto dog = new dog_handler;
78
79  monkey->next(squirrel)->next(dog);
80
81  client(* monkey);
82
83  delete monkey;
84  delete squirrel;
85  delete dog;
86
87  return 0;
88
89}

More to Explore