1. Definition
The Chain of Responsibility pattern decouples request senders from receivers by allowing more than one object to handle a request. The request gets passed along a chain of potential handlers until an object handles it or the end of the chain is reached.
2. Problem Statement
Imagine a scenario where multiple objects can process a request, but only one of them should handle it based on some condition or criteria. Directly linking the requester to the processor can lead to tightly coupled systems and makes it hard to add or change processing objects.
3. Solution
The Chain of Responsibility pattern allows an object to pass the request to a chain of potential handlers until it is processed or the end of the chain is reached. This decouples the sender from the receiver.
4. Real-World Use Cases
1. Event handling systems in GUI libraries, where events can be handled by a component or its parent components.
2. Middleware in web frameworks that processes HTTP requests.
3. Workflow systems where tasks are processed in a sequence or based on certain conditions.
5. Implementation Steps
1. Define a handler interface that declares a method to process the request and to set the next handler in the chain.
2. Create concrete handlers that implement the handler interface. Each handler decides if it can process the request or pass it to the next handler in the chain.
3. Clients create the chain and send the request to the first handler in the chain.
6. Implementation in Java
// Handler interface
interface Handler {
void setNext(Handler handler);
void handleRequest(String request);
}
// ConcreteHandler1
class ConcreteHandler1 implements Handler {
private Handler next;
@Override
public void setNext(Handler handler) {
this.next = handler;
}
@Override
public void handleRequest(String request) {
if ("Request1".equals(request)) {
System.out.println("ConcreteHandler1 handling Request1");
} else if (next != null) {
next.handleRequest(request);
}
}
}
// ConcreteHandler2
class ConcreteHandler2 implements Handler {
private Handler next;
@Override
public void setNext(Handler handler) {
this.next = handler;
}
@Override
public void handleRequest(String request) {
if ("Request2".equals(request)) {
System.out.println("ConcreteHandler2 handling Request2");
} else if (next != null) {
next.handleRequest(request);
}
}
}
// Client
public class Client {
public static void main(String[] args) {
Handler handler1 = new ConcreteHandler1();
Handler handler2 = new ConcreteHandler2();
handler1.setNext(handler2);
handler1.handleRequest("Request1");
handler1.handleRequest("Request2");
handler1.handleRequest("Request3");
}
}
Output:
ConcreteHandler1 handling Request1 ConcreteHandler2 handling Request2
Explanation:
In this example, we have two handlers: ConcreteHandler1 and ConcreteHandler2. When the client sends a request, it's first received by ConcreteHandler1. If it's "Request1", the handler processes it; otherwise, it passes it to the next handler. Similarly, ConcreteHandler2 processes "Request2" and ignores other requests.
7. When to use?
1. When more than one object can handle a request and the handler isn’t known a priori. The handler should be ascertained automatically.
2. When you want to issue a request to one of several objects without specifying the receiver explicitly.
3. When the set of objects that can handle a request should be specified dynamically.