Chain of responsibility design pattern creates a chain of request handlers to process a client's request. Each request handler in chain contains reference of next request handler in the chain. If one request handler cannot handle the request then it passes the request to the next handler in the chain. The request handlers in the chain will decide themselves on runtime that who will process the request and whether request needs to be forwarded to next handler in chain or not.
The request object is sent to a chain of request handler objects. The request can be processed by any request handler in the chain. This pattern comes under behavioral patterns. This pattern promotes loose coupling between senders and receivers of requests.
Structure of the Chain of Responsibility Design Pattern
- Handler : The handler is an interface or an abstract class that defines a method for handling requests. It may also include a reference to the next handler in the chain.
- ConcreteHandler : The concrete handler is a class that implements the handler interface. It contains the actual implementation for handling requests. If it cannot handle a request, it passes the request to the next handler in the chain.
- Client : The client is responsible for creating the chain of handlers and initiating the request. The client sends the request to the first handler in the chain.
Usage of Chain of Responsibility Pattern
- When we want to decouple a request and request handlers.
- When we want a single request processing pipeline that contains multiple possible request handlers arranges in some specific sequence.
- When we don’t want to specify request handlers explicitly in your code, instead want to select appropriate handler on runtime based on request data.
Advantages of Chain of Responsibility Design Pattern
- Flexible Connections : The approach promotes flexible connections between request senders and receivers. Each handler in the chain only requires awareness of its immediate successor, mitigating dependencies and enhancing the system's modularity.
- Adaptability in Request Handling : Handlers can be dynamically introduced, removed, or reordered without impacting the client. This adaptability facilitates seamless adjustments to the handling sequence based on evolving requirements.
- Isolation of Request Handling Logic : Each handler encapsulates the logic for handling a specific request type. This encapsulation facilitates code organization, allowing developers to concentrate on the detailed implementation of individual Handlers.
- Adherence to Single Responsibility Principle : The Chain of Responsibility Design Approach aligns with the Single Responsibility Principle. Each handler bears a distinct responsibility and manages requests within its defined scope.
- Expandability : The approach scales effectively, accommodating new Handlers to the chain without necessitating modifications to existing code. This expandability proves advantageous when confronted with a growing number of potential request handlers.
- Dynamic Configuration of Handlers : Handlers can be configured dynamically, enabling runtime adjustments to the chain. This dynamic configuration facilitates system adaptation to diverse scenarios without requiring alterations to the codebase.
- Simplified Maintenance : Maintenance tasks are simplified as changes to the handling sequence can be executed without modifying existing code. Handlers can be added or modified independently, diminishing the likelihood of introducing errors.
- Efficient Error Management : The chain can be engineered to manage errors effectively. When a handler encounters an error, it can determine whether to handle the error locally or pass it along the chain for additional processing or logging.
Important Points about Chain of Responsibility Pattern
- Client doesn’t know which request handler will process the request and it will just send the request to the first handler in the chain.
- Each request handler in the chain will have it’s own specific implementation to handle the request or to send it to the next object in the chain. Each request handler(except the last handler in chain) must have reference to the next handler in chain.
- The hadlers in the chain should be arranged properly such that a request will reach it's corresponding handler. It is recommended to create a default request handler at the end of chain to process a request If none of the handler can process a request.
Implementation of Chain of Responsibility Pattern
First of all, we will create an enum class called NotificationType for different type of notifications including SMS, EMAIL and MAIL. NotificationType.javapublic enum NotificationType { SMS, EMAIL, MAIL }
AbstractHandler.java
AbstractHandler is an abstract class which contains reference of next handler in chain and declares sendNotification method which sends notification or forward request to next handler in chain. It declares an abstract method sendMessage which is implemented by all notification handlers. Notification handler classes of all three notification types(SMS, EMAIL and MAIL) will have specific implemention for sending different types of notification.
public abstract class AbstractHandler { protected NotificationType messageType; protected AbstractHandler nextHandler; public void setNextHandler(AbstractHandler nextHandler){ this.nextHandler = nextHandler; } public void sendNotification(NotificationType messageType, String message){ if(this.messageType == messageType){ sendMessage(message); return; } if(nextHandler !=null){ System.out.println("Sending message to next handler"); nextHandler.sendNotification(messageType, message); } } abstract void sendMessage(String message); }
SmsNotificationHandler.java
public class SmsNotificationHandler extends AbstractHandler { public SmsNotificationHandler(NotificationType messageType) { this.messageType = messageType; } @Override protected void sendMessage(String message) { System.out.println("Sending message via SMS: " + message); } }
EmailNotificationHandler.java
public class EmailNotificationHandler extends AbstractHandler { public EmailNotificationHandler(NotificationType messageType) { this.messageType = messageType; } @Override protected void sendMessage(String message) { System.out.println("Sending message via Email: " + message); } }
MailNotificationHandler.java
public class MailNotificationHandler extends AbstractHandler { public MailNotificationHandler(NotificationType messageType) { this.messageType = messageType; } @Override protected void sendMessage(String message) { System.out.println("Sending message via Mail: " + message); } }
NotificationHandlerChain.java
NotificationHandlerChain creates an instance of notification handler classes for each notification type and creates a handler chain by setting nextHandler field of each handler. SmsNotificationHandler is the first handler in chain followed by EmailNotificationHandler and then followed by MailNotificationHandler. The sendNotification method will send message to the handlerchain.
public class NotificationHandlerChain { private AbstractHandler smsHandler; private AbstractHandler emailHandler; private AbstractHandler mailHandler; private AbstractHandler handlerChain; public NotificationHandlerChain() { smsHandler = new SmsNotificationHandler(NotificationType.SMS); emailHandler = new EmailNotificationHandler(NotificationType.EMAIL); mailHandler = new MailNotificationHandler(NotificationType.MAIL); // Create handler chain SMS -> EMAIL -> MAIL smsHandler.setNextHandler(emailHandler); emailHandler.setNextHandler(mailHandler); // Set handler chain handlerChain = smsHandler; } public void sendNotification(NotificationType messageType, String message) { handlerChain.sendNotification(messageType, message); } }
Client.java
Client class will first create an instance of NotificationHandlerChain and then send one notification for SMS, EMAIL and MAIL each. Based on the value of NotificationType, corresponding notification handler will send notification else message will be forwarded to next handler in chain.
public class Client { public static void main(String[] args) { NotificationHandlerChain handlerChain = new NotificationHandlerChain(); // Send SMS Notification handlerChain.sendNotification(NotificationType.SMS, "Hello"); // Send Email Notification handlerChain.sendNotification(NotificationType.EMAIL, "Hello"); // Send Mail Notification handlerChain.sendNotification(NotificationType.MAIL, "Hello"); } }
Output
Sending message via SMS: Hello Sending message to next handler Sending message via Email: Hello Sending message to next handler Sending message to next handler Sending message via Mail: Hello
Best Practices of Chain of Responsibility Design Pattern
- Clearly define the responsibilities of each handler in the chain. Handlers should have a well-defined scope and purpose, and their responsibilities should align with their capabilities.
- When assigning the next handler in a concrete handler class, exercise caution. Confirm that the subsequent handler is apt for managing the request, as inappropriate chaining might result in unforeseen outcomes.
- Refrain from creating overly long chains of handlers, as this could diminish performance and complicate system comprehension. Strive for a balanced and reasonable number of handlers in the chain.
- Employ exception handling judiciously, especially when forwarding requests along the chain. In the event of an exception, decide whether to handle it within the current handler or propagate it up the chain.
- Provide a mechanism for default handling when a handler encounters a request it cannot process. This ensures that requests do not go unhandled in the chain, and default handling may involve actions such as logging or client notification.
- Promote loose coupling between handlers by avoiding direct references to concrete implementations of other handlers. Employ interfaces or abstract classes to define the handler contract.
- Steer clear of hard-coding the sequence of handlers in the client or concrete handler classes. Consider implementing a configuration mechanism that facilitates dynamic changes to the handler chain.
- Encourage adherence to the Single Responsibility Principle in handler classes. Each handler should have a single responsibility, fostering a more maintainable codebase and a clean design.
- Formulate clear and concise interfaces for handlers. The handler interface should distinctly outline the method(s) for handling requests and, if applicable, for configuring the next handler.
Bridge Design Pattern |
Template Design Pattern |
Interpreter Design Pattern |
Singleton Design Pattern |
Builder Design Pattern |
List of Design Patterns |