3.4 - Chain of Responsibility Pattern: Behavioral Design Patterns
The Chain of Responsibility Pattern is a behavioral design pattern that allows multiple objects to handle a request in a chain structure. The request gets passed along the chain until it is handled by one of the objects. This pattern promotes loose coupling and allows multiple handlers to process the request without the sender needing to know who will handle it.
Components of the Chain of Responsibility Pattern:
Handler Interface: Declares the method to handle requests and allows setting the next handler in the chain.
Concrete Handlers: Implement the handler interface and define the request handling logic. Each concrete handler is responsible for handling certain types of requests and passing along unhandled requests to the next handler in the chain.
Request: Encapsulates the request details, such as the type or other relevant information.
Client: Creates and configures the chain of handlers, then submits requests for processing.
Code Example
Below is an implementation of the Chain of Responsibility Pattern with renamed class and function names to enhance clarity.
1. Handler Interface
The Processor
interface defines the methods for setting the next processor in the chain and for handling requests.
// Handler Interface
interface Processor {
void setNextProcessor(Processor processor);
void processRequest(TaskRequest request);
}
2. Concrete Handlers
The TaskHandlerA
and TaskHandlerB
classes implement the Processor
interface. Each class processes a specific type of request and forwards unhandled requests to the next processor in the chain.
// Concrete Handler A
class TaskHandlerA implements Processor {
private Processor nextProcessor;
@Override
public void setNextProcessor(Processor processor) {
this.nextProcessor = processor;
}
@Override
public void processRequest(TaskRequest request) {
if (request.getRequestType().equals(TaskType.TYPE_A)) {
System.out.println("TaskHandlerA is processing request of Type A");
// Handle request logic here
} else if (nextProcessor != null) {
nextProcessor.processRequest(request);
}
}
}
// Concrete Handler B
class TaskHandlerB implements Processor {
private Processor nextProcessor;
@Override
public void setNextProcessor(Processor processor) {
this.nextProcessor = processor;
}
@Override
public void processRequest(TaskRequest request) {
if (request.getRequestType().equals(TaskType.TYPE_B)) {
System.out.println("TaskHandlerB is processing request of Type B");
// Handle request logic here
} else if (nextProcessor != null) {
nextProcessor.processRequest(request);
}
}
}
3. Request
The TaskRequest
class represents the request being sent along the chain. It contains details about the request type.
// Request class
class TaskRequest {
private final TaskType requestType;
public TaskRequest(TaskType requestType) {
this.requestType = requestType;
}
public TaskType getRequestType() {
return requestType;
}
}
4. Enum for Request Type
The TaskType
enum defines the types of requests that can be processed.
// Enum for Request Types
enum TaskType {
TYPE_A, TYPE_B
}
5. Client
The ChainDemo
class sets up the chain of responsibility by creating handlers and linking them. It then submits requests to be processed.
// Client class
public class ChainDemo {
public static void main(String[] args) {
// Create handlers
Processor handlerA = new TaskHandlerA();
Processor handlerB = new TaskHandlerB();
// Set up the chain of processors
handlerA.setNextProcessor(handlerB);
// Create requests
TaskRequest request1 = new TaskRequest(TaskType.TYPE_A);
TaskRequest request2 = new TaskRequest(TaskType.TYPE_B);
TaskRequest request3 = new TaskRequest(TaskType.TYPE_A);
// Process requests through the chain
handlerA.processRequest(request1);
handlerA.processRequest(request2);
handlerA.processRequest(request3);
}
}
Explanation:
Processor Interface: The
Processor
interface declares methods for setting the next processor and processing requests. It ensures that all handlers in the chain can pass requests along the chain if they can't handle them.TaskHandlerA and TaskHandlerB: These are concrete handlers that implement the
Processor
interface. Each handler checks if the request type matches what it can handle. If it can, it processes the request; otherwise, it forwards the request to the next processor.TaskRequest: The
TaskRequest
class represents the request and holds information about the request type (eitherTYPE_A
orTYPE_B
).TaskType: An enum that defines the possible types of requests (
TYPE_A
andTYPE_B
).ChainDemo: The client that creates and links the processors, submits requests, and starts the chain.
Benefits of the Chain of Responsibility Pattern:
Loose Coupling: The sender of a request does not need to know which object will handle the request or how many objects are in the chain. It only needs to know the starting point of the chain.
Flexibility: You can dynamically modify the chain by adding, removing, or changing the order of handlers without affecting other parts of the code.
Responsibility Division: This pattern allows you to divide the processing logic across multiple objects, each responsible for handling a specific type of request. This helps with the Single Responsibility Principle.
Scalability: The chain can be easily extended by adding new handlers that can process different types of requests, making it more scalable for complex systems.
Real-World Use Cases:
Technical Support Systems: Requests for technical support are often processed by multiple levels of support, from frontline support agents to specialized experts.
Event Handling: In UI frameworks, events (like mouse clicks or keyboard input) are handled by a chain of components. If one component doesn’t handle the event, it gets passed to the next.
Authentication Middleware: In web applications, authentication and authorization are often processed through a chain of middleware functions, each checking specific security aspects of the request.
Conclusion:
The Chain of Responsibility Pattern is a powerful tool for structuring code where multiple objects may need to handle a request. It simplifies complex decision-making processes, promotes loose coupling, and allows for greater flexibility in request handling. By using this pattern, you can create a robust and maintainable system that can easily evolve to accommodate new types of requests or changes in handling logic.
Subscribe to my newsletter
Read articles from Venu Madhav Emmadi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by