Appearance
Chain of Responsibility
Allow multiple objects the opportunity to handle a request, thereby avoiding coupling between the sender and receiver of the request. Chain these objects into a chain and pass the request along the chain until an object handles it.
The Chain of Responsibility pattern allows multiple handlers the chance to process a request until one successfully handles it. The pattern links multiple handlers into a chain and passes the request along the chain until one handles it:
┌─────────┐
│ Request │
└─────────┘
│
┌ ─ ─ ─ ─ ┼ ─ ─ ─ ─ ┐
▼
│ ┌─────────────┐ │
│ ProcessorA │
│ └─────────────┘ │
│
│ ▼ │
┌─────────────┐
│ │ ProcessorB │ │
└─────────────┘
│ │ │
▼
│ ┌─────────────┐ │
│ ProcessorC │
│ └─────────────┘ │
│
└ ─ ─ ─ ─ ┼ ─ ─ ─ ─ ┘
│
▼
Example: Financial Approval
In real-world scenarios, financial approval is an example of the Chain of Responsibility pattern. Suppose an employee needs to reimburse an expense. The approvers can be:
- Manager: Can only approve reimbursements below 1,000 yuan.
- Director: Can only approve reimbursements below 10,000 yuan.
- CEO: Can approve any amount.
When designing this reimbursement process using the Chain of Responsibility pattern, each approver only concerns themselves with requests within their responsibility range and processes them. For requests exceeding their range, they pass them to the next approver. This way, adding new approvers in the future doesn't require altering existing logic.
Implementing the Chain of Responsibility Pattern
First, abstract the request object that will be passed along the chain:
java
public class Request {
private String name;
private BigDecimal amount;
public Request(String name, BigDecimal amount) {
this.name = name;
this.amount = amount;
}
public String getName() {
return name;
}
public BigDecimal getAmount() {
return amount;
}
}
Next, abstract the handler:
java
public interface Handler {
// Return Boolean.TRUE = Approved
// Return Boolean.FALSE = Denied
// Return null = Pass to the next handler
Boolean process(Request request);
}
Establish a convention: if Boolean.TRUE
is returned, the request is approved; if Boolean.FALSE
is returned, the request is denied; if null
is returned, the request is passed to the next Handler
.
Then, implement ManagerHandler
, DirectorHandler
, and CEOHandler
. For example, ManagerHandler
:
java
public class ManagerHandler implements Handler {
public Boolean process(Request request) {
// If the amount exceeds 1,000 yuan, cannot handle, pass to the next handler:
if (request.getAmount().compareTo(BigDecimal.valueOf(1000)) > 0) {
return null;
}
// Has bias against Bob:
return !request.getName().equalsIgnoreCase("bob");
}
}
After creating different Handler
implementations, combine them into a chain and process requests through a unified entry point:
java
public class HandlerChain {
// Hold all Handlers:
private List<Handler> handlers = new ArrayList<>();
public void addHandler(Handler handler) {
this.handlers.add(handler);
}
public boolean process(Request request) {
// Call each Handler in sequence:
for (Handler handler : handlers) {
Boolean r = handler.process(request);
if (r != null) {
// If TRUE or FALSE is returned, processing ends:
System.out.println(request + " " + (r ? "Approved by " : "Denied by ") + handler.getClass().getSimpleName());
return r;
}
}
throw new RuntimeException("Could not handle request: " + request);
}
}
Now, in the client, assemble the chain and use it to process requests:
java
// Construct the chain:
HandlerChain chain = new HandlerChain();
chain.addHandler(new ManagerHandler());
chain.addHandler(new DirectorHandler());
chain.addHandler(new CEOHandler());
// Process requests:
chain.process(new Request("Bob", new BigDecimal("123.45")));
chain.process(new Request("Alice", new BigDecimal("1234.56")));
chain.process(new Request("Bill", new BigDecimal("12345.67")));
chain.process(new Request("John", new BigDecimal("123456.78")));
The Chain of Responsibility pattern itself is easy to understand. It's important to note that the order in which handlers are added is crucial; if the order is incorrect, the processing results may not meet expectations.
Variations of the Chain of Responsibility Pattern
Additionally, the Chain of Responsibility pattern has several variations. Some implementations allow a handler to manually call the next handler in the chain to pass the request, for example:
java
public class AHandler implements Handler {
private Handler next;
public void process(Request request) {
if (!canProcess(request)) {
// Manually pass to the next Handler:
next.process(request);
} else {
...
}
}
}
Other implementations ensure that each handler has the opportunity to handle the request. This type of chain is often referred to as an Interceptor or Filter, where the goal is not to find a single handler to process the request but to allow each handler to perform some work, such as:
- Logging
- Permission checking
- Preparing related resources
- ...
For example, the Filter
defined in the JavaEE Servlet specification is a form of the Chain of Responsibility pattern. It not only allows each Filter
to decide whether to process the ServletRequest
and ServletResponse
but also whether to "pass through" the request to the next Filter
:
java
public class AuditFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
log(req);
if (check(req)) {
// Pass through:
chain.doFilter(req, resp);
} else {
// Deny:
sendError(resp);
}
}
}
This pattern allows a Filter
not only to decide whether to handle the ServletRequest
and ServletResponse
but also to "forge" ServletRequest
and ServletResponse
so that the next Filter
can process them, enabling the implementation of very complex functionalities.
Exercise
Use the Chain of Responsibility pattern to implement an approval process.
Summary
- The Chain of Responsibility pattern is a way to combine multiple handlers to process a request in sequence.
- The benefit of the Chain of Responsibility pattern is that adding new handlers or rearranging existing handlers is very easy.
- The Chain of Responsibility pattern is often used in intercepting, pre-processing requests, and similar scenarios.