Last Updated on September 24, 2023 by KnownSense
The Chain of Responsibility pattern is a design technique where you have a series of objects, like a chain, and each object can decide whether it can handle a request or should pass it along to the next object in the chain. It’s like a relay race where each runner decides if they can carry the baton forward or pass it to the next runner. This pattern helps in building flexible and loosely coupled systems where you can add or remove handlers easily.
you can find an example of the Chain of Responsibility pattern in the way exceptions are handled using the try-catch
blocks.
try {
// Code that may throw an exception
// ...
} catch (IOException ex) {
// Handle IOException
// ...
} catch (SQLException ex) {
// Handle SQLException
// ...
} catch (Exception ex) {
// Handle other exceptions
// ...
}
In this example:
- The
try
block contains the code that might throw exceptions. - Each
catch
block represents a handler for a specific type of exception. If an exception occurs in thetry
block, Java will check eachcatch
block in order, and the first one that matches the exception type will handle it. - If none of the
catch
blocks can handle the exception, it propagates up the call stack, possibly to higher-level exception handlers.
Benefit
- Decoupling of Senders and Receivers: This pattern decouples the sender of a request from its receivers. Senders don’t need to know which receiver will handle the request, and receivers don’t need to know who sent the request. This promotes a more flexible and maintainable codebase.
- Flexibility and Extensibility: You can easily add, modify, or remove handlers in the chain without affecting the rest of the code. This makes it simple to adapt the system to changing requirements or add new features.
- Single Responsibility Principle: Each handler has a single responsibility, which aligns with the Single Responsibility Principle (SRP) of SOLID design. This makes the code more organized and easier to understand.
- Simplified Client Code: Clients (objects or components that generate requests) don’t need to be aware of the entire processing logic or the specific handlers. They can send requests without worrying about how they are handled.
- Promotes Reusability: Handlers can be reused in different chains or scenarios, promoting code reuse and reducing redundancy.
- Dynamic Handling: You can dynamically assemble and modify chains of responsibility at runtime, which is valuable when the processing logic is complex or depends on runtime conditions.
- Error Handling: It’s commonly used for error and exception handling, where different handlers can deal with specific types of errors or exceptions, allowing for more granular error management.
- Maintenance: As each handler focuses on a specific responsibility, maintenance and debugging are more straightforward. Issues can often be isolated to a specific handler, making it easier to identify and fix problems.
- Scalability: The pattern can handle a varying number of requests and receivers, making it suitable for systems that need to scale in terms of processing.
- Promotes Separation of Concerns: The Chain of Responsibility pattern encourages a separation of concerns by breaking down a complex process into smaller, manageable steps, each handled by a different component.
UML of Chain of responsibility
Implementation
Here’s a simple implementation of the Chain of Responsibility pattern in Java. In this example, we’ll create a chain of handlers that process purchase requests based on their amount. Each handler can approve or reject a request depending on its monetary value. If a handler can’t process the request, it passes it to the next handler in the chain.
Step1: Define the interface PurchaseHandler
representing the handler in the chain.
interface PurchaseHandler {
void setNextHandler(PurchaseHandler nextHandler);
void processRequest(PurchaseRequest request);
}
Step2: Create three concrete handler classes (Manager
, Director
, and VicePresident
) that can approve purchase requests based on their amounts.
class Manager implements PurchaseHandler {
private PurchaseHandler nextHandler;
@Override
public void setNextHandler(PurchaseHandler nextHandler) {
this.nextHandler = nextHandler;
}
@Override
public void processRequest(PurchaseRequest request) {
if (request.getAmount() <= 1000) {
System.out.println("Manager can approve the purchase request #" + request.getRequestNumber());
} else if (nextHandler != null) {
nextHandler.processRequest(request);
} else {
System.out.println("No one can approve the purchase request #" + request.getRequestNumber());
}
}
}
class Director implements PurchaseHandler {
private PurchaseHandler nextHandler;
@Override
public void setNextHandler(PurchaseHandler nextHandler) {
this.nextHandler = nextHandler;
}
@Override
public void processRequest(PurchaseRequest request) {
if (request.getAmount() <= 5000) {
System.out.println("Director can approve the purchase request #" + request.getRequestNumber());
} else if (nextHandler != null) {
nextHandler.processRequest(request);
} else {
System.out.println("No one can approve the purchase request #" + request.getRequestNumber());
}
}
}
class VicePresident implements PurchaseHandler {
private PurchaseHandler nextHandler;
@Override
public void setNextHandler(PurchaseHandler nextHandler) {
this.nextHandler = nextHandler;
}
@Override
public void processRequest(PurchaseRequest request) {
if (request.getAmount() <= 10000) {
System.out.println("Vice President can approve the purchase request #" + request.getRequestNumber());
} else if (nextHandler != null) {
nextHandler.processRequest(request);
} else {
System.out.println("No one can approve the purchase request #" + request.getRequestNumber());
}
}
}
Step3: Create a request class
class PurchaseRequest {
private int requestNumber;
private double amount;
public PurchaseRequest(int requestNumber, double amount) {
this.requestNumber = requestNumber;
this.amount = amount;
}
public int getRequestNumber() {
return requestNumber;
}
public double getAmount() {
return amount;
}
}
Step4: In main class, create a chain of responsibility by linking the handlers together in a specific order.
public class ChainOfResponsibilityDemo {
public static void main(String[] args) {
// Create handlers
PurchaseHandler manager = new Manager();
PurchaseHandler director = new Director();
PurchaseHandler vicePresident = new VicePresident();
// Set up the chain
manager.setNextHandler(director);
director.setNextHandler(vicePresident);
// Create purchase requests
PurchaseRequest request1 = new PurchaseRequest(1, 800);
PurchaseRequest request2 = new PurchaseRequest(2, 2500);
PurchaseRequest request3 = new PurchaseRequest(3, 12000);
// Process requests
manager.processRequest(request1);
manager.processRequest(request2);
manager.processRequest(request3);
}
}
OUTPUT:
Manager can approve the purchase request #1
Director can approve the purchase request #2
Vice President can approve the purchase request #3
Conclusion
In conclusion, the Chain of Responsibility pattern offers a flexible way to process requests or events through a chain of handlers, with each handler having the ability to handle the request or pass it to the next handler. This pattern promotes decoupling, extensibility, and modularity in software design, making it easier to manage complex workflows and handle varying conditions efficiently.