The Chain of Responsibility pattern consists of the following key components:
A handler is an object that can process a request. It has a reference to the next handler in the chain. If a handler cannot process the request, it passes the request to the next handler in the chain.
A concrete handler is a specific implementation of the handler interface. It decides whether it can process the request or not. If it can, it processes the request; otherwise, it passes the request to the next handler.
The client is the object that initiates the request. It does not need to know which handler will process the request. It simply sends the request to the first handler in the chain.
The request is the object that contains the information about the operation to be performed. It is passed along the chain of handlers.
Here is a simple UML diagram to illustrate the Chain of Responsibility pattern:
@startuml
interface Handler {
+handleRequest(request: Request): void
}
class ConcreteHandler1 {
-nextHandler: Handler
+handleRequest(request: Request): void
}
class ConcreteHandler2 {
-nextHandler: Handler
+handleRequest(request: Request): void
}
class Client {
+sendRequest(request: Request): void
}
class Request {
-data: String
}
Handler <|.. ConcreteHandler1
Handler <|.. ConcreteHandler2
ConcreteHandler1 *-- Handler : nextHandler
ConcreteHandler2 *-- Handler : nextHandler
Client --> Handler : send request
@enduml
Let’s implement the Chain of Responsibility pattern in Java with a simple example of handling different types of requests.
class Request {
private String requestType;
public Request(String requestType) {
this.requestType = requestType;
}
public String getRequestType() {
return requestType;
}
}
interface Handler {
void handleRequest(Request request);
void setNextHandler(Handler nextHandler);
}
class ConcreteHandler1 implements Handler {
private Handler nextHandler;
@Override
public void handleRequest(Request request) {
if ("Type1".equals(request.getRequestType())) {
System.out.println("ConcreteHandler1 handled the request of type " + request.getRequestType());
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
} else {
System.out.println("No handler can handle the request of type " + request.getRequestType());
}
}
@Override
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
}
class ConcreteHandler2 implements Handler {
private Handler nextHandler;
@Override
public void handleRequest(Request request) {
if ("Type2".equals(request.getRequestType())) {
System.out.println("ConcreteHandler2 handled the request of type " + request.getRequestType());
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
} else {
System.out.println("No handler can handle the request of type " + request.getRequestType());
}
}
@Override
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
}
class Client {
private Handler firstHandler;
public Client(Handler firstHandler) {
this.firstHandler = firstHandler;
}
public void sendRequest(Request request) {
firstHandler.handleRequest(request);
}
}
public class Main {
public static void main(String[] args) {
Handler handler1 = new ConcreteHandler1();
Handler handler2 = new ConcreteHandler2();
handler1.setNextHandler(handler2);
Client client = new Client(handler1);
Request request1 = new Request("Type1");
client.sendRequest(request1);
Request request2 = new Request("Type2");
client.sendRequest(request2);
Request request3 = new Request("Type3");
client.sendRequest(request3);
}
}
In this example, the client sends requests to the first handler in the chain. Each handler checks if it can handle the request. If it can, it processes the request; otherwise, it passes the request to the next handler.
In a real - world scenario, it is important to handle errors properly when a request cannot be handled by any handler in the chain. You can throw an exception or log an error message.
The chain can be configured dynamically at runtime. For example, you can add or remove handlers from the chain based on certain conditions.
You can add logging statements in each handler to track the flow of requests through the chain. This can be useful for debugging and auditing purposes.
Each handler should be independent and have a single responsibility. This makes the code more modular and easier to maintain.
Make sure that there are no circular references in the chain. A circular chain can lead to an infinite loop when processing requests.
Use interfaces to define the handler contract. This allows for more flexibility and makes it easier to swap out different implementations of handlers.
The Chain of Responsibility pattern is a powerful design pattern that provides a flexible and maintainable way to handle requests. By decoupling the sender of a request from its receivers, it allows for easy modification and extension of the handling logic. In Java, it can be implemented using interfaces and classes, and it can be applied in various scenarios such as event handling, authentication, and logging. By following the common practices and best practices, you can ensure that your implementation of the Chain of Responsibility pattern is robust and efficient.
To view the PlantUML diagram, you can use an online PlantUML editor such as https://www.planttext.com/ or install a PlantUML plugin in your IDE.