Chain of Responsibility Design Pattern in Java

Chain of Responsibility Design Pattern in Java

In this article, I am going to discuss the Chain of Responsibility Design Pattern in Java with Examples. Please read our previous article where we discussed Structural Design Patterns with Examples. The Chain of Responsibility Design Pattern falls under the category of Behavioral Design Pattern. In this article, we will explore the fundamental principles and benefits of the Chain of Responsibility design pattern, showcasing its significance in various software development scenarios.

What is a Chain of Responsibility Design Pattern?

According to the Gang of Four Definitions, the Chain of Responsibility Design Pattern states that Avoid coupling the sender of a request to its receiver by giving more than one receiver object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handle it.

In software development, handling and processing requests in a flexible and decoupled manner is a common challenge. The Chain of Responsibility design pattern offers an elegant solution to this problem by establishing a chain of objects, where each object has the opportunity to handle a request or pass it to the next object in the chain. This pattern promotes loose coupling and enhances the flexibility of object interactions.

The Chain of Responsibility design pattern is a behavioral pattern that allows multiple objects to have a chance to handle a request. The pattern creates a chain of objects, where each object has a reference to its successor. When a request is made, the first object in the chain attempts to handle it. If the object can handle the request, it does so; otherwise, it passes the request to the next object in the chain. This process continues until the request is handled or reaches the end of the chain. This pattern provides a flexible and dynamic way to process requests, with each object in the chain having the ability to modify the request or completely bypass it.

Example to Understand the Chain of Responsibility Design Pattern:

Please have a look at the following diagram to understand the Chain of Responsibility Design Pattern. As shown in the below diagram, on the left side we have a client and on the right side, we have multiple receivers chained together (i.e. receiver 1 has a reference to receiver 2, and receiver 2 has a reference to receiver 3, and so on).

Example to Understand the Chain of Responsibility Design Pattern

Here the client sends the request to the Chain of Receivers. In the chain of receivers, the first receiver is Receiver 1. So, the request will come to Receiver 1, and Receiver 1 will check whether it can handle the request or not. If it can handle the request then it will handle the request and then check whether further processing is needed for the request or not. If further processing is needed then it will send the request to the next receiver (i.e. Receiver 2) in the chain of receivers. If the request does not need any further processing, then it will not pass the request to the next receiver.

Receiver 2 will then check whether it can handle the request or not. If it can handle the request then it will handle the request and then check whether further processing is needed or not. If further processing is needed then it will send the request to the next receiver (i.e. Receiver 3). If it won’t need further processing then it will not pass the request to the next receiver. In this way, the chain of responsibility works.

Implementing Chain of Responsibility Design Pattern in Java

Consider a customer support system where customer requests are processed through a chain of handlers. The first handler in the chain handles basic inquiries, while subsequent handlers address more specific issues such as technical support or billing inquiries. If a handler cannot resolve the request, it passes it to the next handler in the chain. This scenario demonstrates how the Chain of Responsibility pattern can effectively handle diverse customer requests while allowing for flexible customization and dynamic handling. The UML Diagram of this example is given below using the Chain of Responsibility Design Pattern.

Implementing Chain of Responsibility Design Pattern in Java

Step 1: Create a new directory to store all the class files of this project.

Step 2: Open VS Code and create a new project, called chainofresponsibility.

Step 3: In the project, create a new file called Complaint.java. Add the following code to the file:

public abstract class Complaint
{
    public static int EMPLOYEE = 1, AMANAGER = 2, MANAGER = 3;

    protected int level;

    protected Complaint nextLevelComplaint;

    public void setNextLevelComplaint(Complaint nextLevelComplaint)
    {this.nextLevelComplaint = nextLevelComplaint;}

    public void makeComplaint(int level, String complaint)
    {
        if (this.level == level)
            handleComplaint(complaint);
        else if (nextLevelComplaint != null)
            nextLevelComplaint.makeComplaint(level, complaint);
    }

    protected abstract void handleComplaint(String complaint);
}

This is the abstract from which other concrete classes will extend.

Step 4: In the project, create a new file called Employee.java. Add the following code to the file:

public class Employee extends Complaint
{
    public Employee (int level) {this.level = level;}

    @Override
    protected void handleComplaint(String complaint)
    {System.out.println("Employee is handling the complaint: " + complaint);}
}

Step 5: In the project, create a new file called AManager.java. Add the following code to the file:

public class AManager extends Complaint
{
    public AManager (int level) {this.level = level;}

    @Override
    protected void handleComplaint(String complaint)
    {System.out.println("Assistant manager is handling the complaint: " + complaint);}
}

Step 6: In the project, create a new file called Manager.java. Add the following code to the file:

public class Manager extends Complaint
{
    public Manager (int level) {this.level = level;}

    @Override
    protected void handleComplaint(String complaint)
    {System.out.println("Manager is handling the complaint: " + complaint);}
}

Step 7: In the project, create a new file called ChainOfResponsibilityDemo.java. This class will contain the main() function. Add the following code to ChainOfResponsibilityDemo.java:

public class ChainOfResponsibilityDemo
{
    public static void main(String[] args)
    {
        Complaint c = chain();
        
        c.makeComplaint(Complaint.EMPLOYEE, "This complaint is directed to the employee.");
        c.makeComplaint(Complaint.AMANAGER, "This complaint is directed to the assistant manager.");
        c.makeComplaint(Complaint.MANAGER, "This complaint is directed to the manager.");
    }

    private static Complaint chain()
    {
        Complaint employee = new Employee(Complaint.EMPLOYEE);
        employee.setNextLevelComplaint(new AManager(Complaint.AMANAGER));
        employee.nextLevelComplaint.setNextLevelComplaint(new Manager(Complaint.MANAGER));
        return employee;
    }
}

The chain() function is used to “chain” the levels and arranges them. This is basically configuring which level comes first, which one comes after that, and so on. The main() function calls the chain() function. This initializes the Complaint variable. Finally, the main function makes three hardcoded complaints, one for each level.

Step 8: Compile and execute the application. Ensure compilation is successful. Verify that the program works as expected.

Chain of Responsibility Design Pattern in Java with Examples

Congratulations! You now know how to implement the Chain of Responsibility Design Pattern in Java.

UML Diagram of Chain of Responsibility Design Pattern:

Now, let us see the Chain of Responsibility Design Pattern UML Diagram Components with our Example so that you can easily understand the UML Diagram.

UML Diagram of Chain of Responsibility Design Pattern

The classes can be described as follows:

  1. Object: This class contains the definitions for the functions. These functions will later be implemented in the concrete classes.
  2. ConcreteObject: This class implements the functions defined in aforementioned interface.
  3. DriverClass: This class contains the main() function and is responsible for handling the simulation of the program.
Advantages of Chain of Responsibility Design Pattern in Java

The advantages of using the Chain of Responsibility Design Pattern in Java are as follows:

  • Decoupling of Requesters and Handlers: The Chain of Responsibility pattern decouples requesters from the specific handlers. Requesters only need to know about the first object in the chain, and the rest of the chain is transparent to them. This loose coupling allows for easy modification and reordering of handlers without affecting the requesters, promoting flexibility and maintainability.
  • Simplified Object Interactions: The pattern simplifies the interactions between objects by removing the need for explicit dependencies between requesters and handlers. Each object only needs to know about its immediate successor in the chain, reducing the complexity of object collaborations and allowing for more manageable and modular code.
  • Dynamic and Extensible Processing: The Chain of Responsibility pattern enables dynamic and extensible processing of requests. New handlers can be added or removed from the chain without modifying existing code. This flexibility allows for easy customization and adaptation to changing requirements, making the pattern suitable for evolving systems.
  • Single Responsibility Principle: The pattern promotes adherence to the Single Responsibility Principle by assigning a specific responsibility to each handler in the chain. Each handler focuses on handling a particular type of request or condition, ensuring that objects have clear and focused responsibilities. This principle facilitates code maintenance, testing, and understanding.
  • Fault-Tolerant and Error Handling: The Chain of Responsibility pattern facilitates fault-tolerant systems and error handling. If a handler fails to handle a request, it can pass it to the next handler or perform error recovery operations. This fault-tolerant behavior allows for graceful degradation and resilience in the face of failures or exceptional conditions.
Disadvantages of Chain of Responsibility Design Pattern in Java

The disadvantages of using the Chain of Responsibility Design Pattern in Java are as follows:

  • Potential for Request Mishandling: In a chain of responsibility, there is a possibility that a request may go unhandled if no handler in the chain can effectively process it. This situation can lead to requests being dropped or overlooked, resulting in unexpected behavior or missing functionality. Careful design and configuration of the chain are necessary to ensure that all requests are properly handled or have a designated fallback mechanism.
  • Performance Impact: The Chain of Responsibility pattern can introduce a performance overhead due to the iterative nature of request propagation through the chain. Each handler in the chain needs to evaluate whether it can handle the request or pass it to the next handler. This evaluation process can add processing time and introduce unnecessary complexity, especially if the chain contains a large number of handlers.
  • Lack of Centralized Control: The decentralized nature of the Chain of Responsibility pattern may result in a lack of centralized control and coordination. With multiple handlers potentially involved in processing a request, it can be challenging to maintain a holistic view of the system behavior and ensure consistent handling across all handlers. This lack of central control may make it more difficult to enforce global policies, perform logging or auditing, or coordinate actions that span multiple handlers.
  • Difficulty in Determining the Handler: When using the Chain of Responsibility pattern, it may be challenging to determine which handler in the chain will handle a particular request. Since the requester is unaware of the entire chain structure, it may not know which handler will process the request. This lack of visibility can make it harder to trace and debug the flow of requests through the chain, especially in complex systems with intricate chains of responsibility.
  • Potential for Excessive or Inefficient Handler Chaining: Improper design or configuration of the chain can lead to excessive or inefficient chaining of handlers. If the chain is too long or contains redundant or overlapping responsibilities, it can result in unnecessary processing and decreased performance. Additionally, if the chain is not properly ordered or prioritized, it may lead to inefficient handling of requests, where lower-priority handlers are unnecessarily involved in processing requests that could have been handled by higher-priority ones.
  • Tight Coupling to the Chain: In some implementations of the Chain of Responsibility pattern, handlers may be tightly coupled to the chain structure. This tight coupling can make it harder to modify or extend the chain dynamically at runtime. If changes to the chain structure are required, it may involve modifications to the existing code or dependencies on specific handler implementations, which can impact flexibility and maintainability.

In the next article, I am going to discuss Command Design Pattern in Java with Examples. Here, in this article, I try to explain the Chain of Responsibility Design Pattern in Java with Examples. I hope you understood the need for and use of the Chain of Responsibility Design Pattern in Java.

Leave a Reply

Your email address will not be published. Required fields are marked *