Chain of Responsibility is a behavioral design pattern which allows you to pass requests along a chain of objects. Each of these objects can either be able to handle the request or pass it along the chain until it gets handled.
What is Chain of Responsibility Pattern?
This is a design pattern where we can have several layers of hierarchy to handle user requests. It allows multiple entries to handle requests. For example, if we want to create a hierarchy of responsibility, we can use this pattern to build such hierarchy where the lower level executives can handle low priority requests and upper level can handle higher value or higher priority requests. The customer service is a great example of this system. When a customer needs to cancel a request or get a refund for their orders, they may go through such chain. If the order is relatively recent and the order amount is not high, the customer service representive could be able to cancel or refund the order. If there are disputes or the amount of order value is large, it may have to go through other hierarchy such as manager or finance department in order to approve the refunds.
This pattern is useful when we want to execute several handlers in a particular order. This also allows to set handlers at runtime. It’s a great pattern for decoupling the sender and receiver. The client does not need to know who actually handled the request.
How to implement Chain of Responsibility?
This pattern mainly consists of following components:
- Abstract Handler: This is an interface or abstract class which defines two methods. The
setSuccessor()method defines the nextsuccessorwho can handle client requests if this handler is unable to handle it. Another methodhandle(request)is implemented by concrete handlers based on request information. We can definesetSuccessor()in this abstract class itself unless we need to handle different kind of logic in which case, we can override the behavior of this method in concrete handlers. - Concrete Handler: These are actual handlers of the requests. They are of the type
Handlersuch that each handler can look and behave like other handlers. These handlers extend abstract handler and implement actual logic for how to handle the incoming request. - Client: The client code can compose chain in the code or application logic. The request will pass through chain of objects. The first handler does not necessarily have to handle the request. It can simply pass the request to next handler.
Practical Example - Order Refunds/Cancellation
- Create abstract handler with two methods. One
abstractmethod to handle incoming requests and another to set the next successor. In below code,handleRequest(request)is used to handle the requests andnextSuccessor(successor)is used to set the next successor. Notice that, the second method is implemented in abstract handler, but it could be overridden.
1public abstract class Handler {
2
3 protected Handler successor;
4
5 public void nextHandler(Handler successor) {
6 this.successor = successor;
7 }
8
9 public abstract void handleRequest(Request request);
10}
- Define
Requestobjects that we want to handle. This code is for handling only two types of requests: cancellation and refund of orders.
1public enum RequestType {
2 CANCELLATION, REFUND;
3}
1public class Request {
2
3 private RequestType requestType;
4 private double amount;
5
6 public Request(RequestType requestType, double amount) {
7 this.requestType = requestType;
8 this.amount = amount;
9 }
10
11 public RequestType getRequestType() {
12 return requestType;
13 }
14
15 public double getAmount() {
16 return amount;
17 }
18}
- Create concrete handlers which extend the
Handlerand implement custom logic for handling requests. In this case, I have three concrete handlers to handle customer refund or cancellation requests.
1public class CustomerServiceExecutive extends Handler {
2 @Override
3 public void handleRequest(Request request) {
4 if(
5 (request.getRequestType() == RequestType.CANCELLATION && request.getAmount() <= 1000) ||
6 (request.getRequestType() == RequestType.REFUND && request.getAmount() <= 500)
7 ) {
8 System.out.printf("Customer service executive has approved this request for amount $%.2f.\n", request.getAmount());
9 } else {
10 System.out.println("Customer service executive has escalated this request to supervisor.");
11 successor.handleRequest(request);
12 }
13 }
14}
Notice that CustomerServiceExecutive the handleRequest() can handle refund requests upto $500 and cancellation requests upto $1000. For other requests, it will pass the request to its successor which in this case will be Supervisor
1public class Supervisor extends Handler {
2
3 @Override
4 public void handleRequest(Request request) {
5 if(
6 (request.getRequestType() == RequestType.CANCELLATION && request.getAmount() <= 5000) ||
7 (request.getRequestType() == RequestType.REFUND && request.getAmount() <= 2000)
8 ) {
9 System.out.printf("Supervisor executive has approved this request for amount $%.2f.\n", request.getAmount());
10 } else {
11 System.out.println("Supervisor has escalated this request to manager.");
12 successor.handleRequest(request);
13 }
14 }
15}
Again, Supervisor can handle requests upto certain value and if the request is for larger amount, it will pass the request to its successor Manager.
1public class Manager extends Handler {
2 @Override
3 public void handleRequest(Request request) {
4 if (request.getAmount() <= 10000) {
5 System.out.println("Manager has approved this request.");
6 } else {
7 System.out.println("Manager can only provide credits upto 10000. Please file a complaint and billing department will get back to you.");
8 }
9 }
10}
If Manager cannot handle the request, it will need to be escalated to billing department and they will investigate on this and revert back with resolution to the customer.
The client code of this pattern might look something like this.
1public class Main {
2 public static void main(String[] args) {
3 CustomerServiceExecutive customerServiceExecutive = new CustomerServiceExecutive();
4 Supervisor supervisor = new Supervisor();
5 Manager manager = new Manager();
6
7 customerServiceExecutive.nextHandler(supervisor);
8 supervisor.nextHandler(manager);
9
10 customerServiceExecutive.handleRequest(new Request(RequestType.CANCELLATION, 1000));
11 customerServiceExecutive.handleRequest(new Request(RequestType.CANCELLATION, 5001));
12 }
13}
Advantages:
- This promotes loose coupling between sender and receiver of the requests.
- When we want to handle requests differently, we can set the hierarchy of handlers at runtime using configuration or factory classes.
- You can add new handlers easily in the hierarchy without affecting other handler code or tests. This follows Open/Closed principle
Disadvantages:
- Some requests may not have any defined handlers and go unhandled.
- If the chain length is too big, it may result in performance overheads.
Comparison with Command Pattern
| Chain of Responsibility Pattern | Command Pattern |
|---|---|
| deals with user requests using handlers | commands are also unique |
| This is used to create hierarchy of handlers | This is used to encapsulate requests |
| The order is defined at runtime, but not reversible once executed. | Commands on the other hand, are reversible |
Summary
- CoR is a great pattern for decoupling sender and receiver.
- It helps provide runtime configurations for setting different handlers.
- Chain of responsibility can help set up hierarchical nature of handlers in your design.
- Large chains should be avoided and there is no guarantee that all requests will be handled.


Comments