Understanding the Chain of Responsibility Design Pattern

There are 23 classic design patterns described in the original book Design Patterns: Elements of Reusable Object-Oriented Software. These patterns provide solutions to particular problems often repeated in software development.

In this article, I am going to describe how the Chain of Responsibility pattern works and when it should be applied.


Responsibility of Chain: Basic Idea

Wikipedia provides us with the following definition:

“The chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain. A mechanism also exists for adding new processing objects to the end of this chain.” — Wikipedia

On the other hand, the definition provided by the original book is as follows:

“Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.”

On many occasions, we have a set of handlers (operations) that can be applied to an object (request) but we do not know, a priori, which handler should be applied to said object, nor whether one or more handlers should be applied to the request. The chain of responsibility pattern allows us to achieve more efficient and less coupled code since it avoids the previously mentioned issue. It also has other advantages regarding code maintainability. Here’s the UML pattern of this pattern:

These are the classes that comprise this pattern:

  • Handler is the interface for handling requests. Although optional, most implementations also specify the successor link.

  • ConcreteHandler are the concrete implementations that are responsible for handling the requests. Also, it can access its successor. The behavior of these classes is quite simple: if it can handle the request it will do it, otherwise, it will delegate the responsibility to the next handler.

  • Client is responsible for initiating the sequence of request handlers (ConcreteHandler).


Responsibility of Chain: When to Use It

  • When there is more than one object that can handle a request, and it happens that the handler is not known a priori. Also, we need the handler to be selected automatically.

  • The set of objects that can handle a request can be specified dynamically.

  • A request is handled by one or more objects without explicitly specifying the recipient.


Chain of Responsibility Pattern: Advantages and Disadvantages

The chain of responsibility pattern has a number of advantages, summarized in the following points:

  • The code is more maintainable because it is less coupled between the object and the other object handles a request. The object (sender) only needs to know that a request will be handled by the handler. That is to say that both (receiver and the sender) have no explicit knowledge of each other. Besides, the object in the chain doesn’t need to know about the chain’s structure.

  • Clean code. The Open-Closed Principle (OCP) is guaranteed since new handlers can be introduced without breaking the existing code in the chain.

  • Cleaner code. The Single Responsibility Principle (SRP) is respected since the responsibility of each handler is transferred to its handle method instead of having that business logic in the client code.

A well-known drawback of this pattern is that the receipt isn’t guaranteed. That’s due to the fact that the request has no explicit receiver. Therefore, the request can fall off the end of the chain without ever being handled.

Finally, the main drawback of the chain of responsibility pattern — like most design patterns — is that there’s an increase in code complexity and the number of classes required for the code. With that said, this disadvantage is well known when applying design patterns — it’s the price to pay for gaining abstraction in the code.


Responsibility of Chain Examples

Next, we are going to illustrate with two examples of the Responsibility of Chain pattern:

  • The basic structure of the Responsibility of Chain pattern. In this example, we are going to translate the theoretical UML diagram into TypeScript code to identify each of the classes involved in the pattern.

  • A middleware-based authentication system. Three middlewares are developed that allow validating the existence of a user, password-based authentication, and finally the role of the user.

The following examples will show the implementation of this pattern using TypeScript. We have chosen TypeScript to carry out this implementation rather than JavaScript. The latter lacks interfaces or abstract classes, so the responsibility of implementing both the interface and the abstract class would fall on the developer.


Example 1: Basic Structure of the Responsibility of Chain Pattern

In this first example, we’re going to translate the theoretical UML diagram into TypeScript to test the potential of this pattern. This is the diagram to be implemented:

First, we are going to define the interface (Handler) of our problem. This interface defines two methods next and handle. The first method is in charge of linking each handler with the next. While the second method is in charge of carrying out the specific operation of each of the handlers.

The next class to define is AbstractHandler. This class implements the interface and is, by definition, abstract — because there can be methods that are implemented in concrete classes. Normally, the abstract method is the one that handles the request (handler). However, in our concrete example we have implemented this function in such a way as to check that if there is a following handler, pass it on the responsibility of handling the request, and in the event that there are no more handlers, we simply stop in the chain of responsibility.

Another interesting point is to see how there is a reflexive relationship that allows defining the nextHandler attribute, which is the next handler in the chain. Finally, this class defines the assignment accessor method for the nextHandler attribute.

The next step is to define each of the concrete handlers, which will extend from the abstract class but will have concrete implementations of the handle method. Specifically, we can see that in the basic example what we do is check in each of the handlers if the request they receive corresponds to the one they know how to manage, and if not, we derive the responsibility to the next handler of the chain of responsibility through the method implemented in the AbstractHandler class.

Finally, we have to define the client code, which makes use of this pattern. In this case, we are going to declare the three handlers (HandlerA, HandlerB and HandlerC), and assign the order of the following responsibility chain the following order: A → B → C.

Later, we’ll simulate three requests for each of the handlers through the auxiliary function ClientCode.

In the first case, you can see how the three options have been managed and the managers who are responsible for them.

In the second case, we are going to omit HandlerA from the chain of responsibility and repeat the three requests again. The result will be that neither HandlerB nor HandlerC is able to handle the request corresponding to OptionA.


Example 2: Middlewares Using Chain of Responsibility

In this example, we are going to use the chain of responsibility pattern to simulate an authentication and authorization system using a set of middlewares that applies checks on a request.

As we did in the previous example, let’s start by taking a look at the UML diagram that is going to help us identify each of the parts that this pattern is composed of.

Notice that our class diagram incorporates some extra classes. These give context to our problem, but the chain of responsibility pattern can easily be identified among the set of classes.

Before we address the implementation of our problem, let’s define a set of constants that will give us semantic value to these values. These three constants are quite simple: Firstly, we have two fake users (USERS), secondly, the number of maximum authentication requests per minute (REQUEST_PER_MINUTE), and finally the waiting time in milliseconds when the number of requests per minute is exceeded (WAIT_TIME).

We start to define the pattern again by defining the Handler interface with the same two methods as in the basic structure of the pattern. In other words, the Handler interface is made up of the setNextMiddleware and execute methods, the first being in charge of linking each of the middleware and the second method is responsible for handling the request.

In our case, the request that we have to handle is the one composed by a User.

The objects that behave as users must satisfy the User interface, which is simply composed of an email and a password. The goal of this tutorial is to understand the chain of responsibility pattern, so this part will be simple — but complete enough to solve the authentication and authorization problem.

Continuing with the pattern, the next element is the development of the abstract class, Middleware, that implements the Handler interface. In this implementation, there is the setNextMiddleware method, which allows linking the following middleware, and the execute method, which is abstract and is implemented in each of the specific middleware. Finally, we have the checkNext method that checks if we have reached the end of the middleware chain, responding true when all the checks have been passed. If there is a next middleware then the next middleware check will be executed.

The first middleware we need to build for the authentication and authorization system is verification that the user exists.

In our example, we have modeled a fake server where we have two registered users (the same ones that we previously defined in the constants). This server, which we will see later, provides us an API with the hasEmail and isValidPassword methods. The execute method will receive the request (a User) and we would make the checks to ensure the user exists on the server. First, it checks if the email exists on the server; if so, the username and password are checked. If the check is passed, the checkNext method is executed, which consists of making the request to the next middleware.

The following middleware would be the one that allows us to control the number of requests per minute.

This is where you can see the Open-Close Principle (OCP) — it is very easy to incorporate new middleware and checks without breaking the existing code. You can also see the Single Responsibility Principle(SRP) since each middleware has only one responsibility.

In this middleware, we receive the maximum number of requests per minute as a parameter. In addition, this middleware has some private attributes that allow you to control the time that has passed between requests, and the number of attempts that have been made in one minute.

If you look at the logic of the execute method, it’s quite simple — but you can check how a check exists again. This is what stops the chain of responsibility since it’s resolved by this handler.

In case the check is passed, this middleware, like the previous one, will proceed to pass the request to the next middleware.

Finally, the following middleware is in charge of checking if the user is an administrator or not, obviously, this should have been done with a real check on a knowledge base. However, to illustrate that each middleware can have different parameters, a basic check has been performed.

In the case of being admin, the chain of responsibility is finished. Otherwise, it continues. This is so that when a new middleware is developed, it can be managed in the chain. In our case, here we end the responsibility chain because we do not have any more middleware implemented.

For educational purposes, we have built a Server class that stores users on a Map<String, User> and has a Middleware that is the one that begins the chain of responsibility.

The register, hasEmail and isValidPassword methods are focused on performing these operations using the users who are registered on the server.

The logIn method receives the request with the user. The chain of responsibility begins on line 13 with the execution of the middleware sending the user as a request.

Finally, the client that makes use of our middleware system is the one shown in the code. A server has been created and two user accounts registered, obviously this would be our real backend and should not have been done here.

Subsequently, the chain of responsibilities is indicated using the following middleware in the following order:

UserExists -> Trottling -> Role

Finally, we have created a loop in which email and password are requested.

To end this article we are going to see the code working and for this I have recorded several GIFs.

In the first, we can see the UserExists and Throttling middleware in operation. I have entered the wrong email and password several times, the first middleware (Throttling) will leave the responsibility the first two times to the UserExists middleware, which rejects the validation of the user/password. From the third time the credentials are entered, the Throttling middleware will be in charge of managing the request.

The role middleware is the one that manages the request when the credentials corresponding to the admin role are entered.

Finally, I’ve created two npm scripts through which the code presented in this article can be executed:

    npm run example1
    npm run example2

See this GitHub repo for the full code.


Conclusion

Chain of responsibility is a design pattern that allows you to respect the Open-Closed Principle since a new Handler can be created without breaking the existing code. In addition, this allows you to comply with the Single Responsibility Principle (SRP) since each handler only has a single responsibility to resolve. Another very interesting point of this pattern is that it is not necessary to know, a priori, which handler should resolve the request, which allows you to make the decision at runtime.

The most important thing about this pattern is not the concrete implementation of it but the ability to recognize the problem that this pattern can solve and when it can be applied. The specific implementation isn’t as important since that will vary depending on the programming language used.