Chain of Responsibility
Pass a request along a chain of handlers, where each one can deal with it or hand it to the next.
- Behavioral
- Middleware that checks a web request
- Java code
The idea
A request often needs several checks before it can be served. Is the user signed in? Is their IP address allowed? Have they sent too many requests? Putting all of these checks in one method makes it long, and every new check means editing it.
The Chain of Responsibility pattern puts each check in its own handler object. Handlers share one interface, and each holds a reference to the next handler. The sender passes the request to the first handler and doesn't know which handler, if any, will deal with it.
In the Gang of Four version, each handler either handles the request or passes it on. A common variant, used in this example and in web framework middleware, lets every handler run its check and pass the request on, while any handler can stop it.
When to use it
- A request has to go through several checks or steps, and you want each one in its own class.
- More than one object may handle a request, and the handler should be chosen at runtime.
- You want to add, remove or reorder steps without changing the sender.
- You are building middleware for web requests, or UI events that move from a control up to its parents.
Pros and cons
Pros
- It decouples the sender of a request from the objects that handle it.
- You can change the chain at runtime by adding, removing or reordering handlers.
- Each handler does one job, so it is easy to test on its own.
Cons
- It can be hard to follow: to know what happens to a request, you have to trace the whole chain.
- A long chain can slow things down, and a request may reach the end without being handled.
The example
The example checks an incoming web request with three middleware steps: authentication, an IP allow list and throttling. If every step passes, the request is valid.
Middleware: the handler. An abstract class that holds the next handler, sets it withsetNextHandler(), and offershandleNext(), which passes the request on, or returnstrueat the end of the chain.AuthenticateMiddleware,WhitelistIpMiddlewareandThrottlingMiddleware: concrete handlers. Each prints what it checks, then either returnsfalseto stop the request or callshandleNext()to pass it on. The checks themselves are stubs that always pass.Demo: the client. It builds the chain and asks whether the request is valid.
The code
public abstract class Middleware {
private Middleware next;
public Middleware setNextHandler(Middleware next) {
this.next = next;
return next;
}
public abstract boolean handle();
protected boolean handleNext() {
if (next == null) {
return true;
}
return next.handle();
}
}
public class AuthenticateMiddleware extends Middleware {
@Override
public boolean handle() {
System.out.println("check if user is authenticated");
if (!checkIfAuthenticated()) {
System.out.println("User is not authenticated");
return false;
}
return handleNext();
}
private boolean checkIfAuthenticated() {
return true;
}
}
public class WhitelistIpMiddleware extends Middleware {
@Override
public boolean handle() {
System.out.println("check if ip in database or not");
if (!checkIp()) {
System.out.println("Ip is not in database");
return false;
}
return handleNext();
}
private boolean checkIp() {
// code to check if IP is in database
return true;
}
}
public class ThrottlingMiddleware extends Middleware {
private final int requestPerMinute;
private final int request;
public ThrottlingMiddleware(int requestPerMinute, int request) {
this.requestPerMinute = requestPerMinute;
this.request = request;
}
@Override
public boolean handle() {
System.out.println("handle the functionality for throttling");
if (checkThrottling()) {
System.out.println("Request is throttled");
return false;
}
return handleNext();
}
private boolean checkThrottling() {
// code to check if request is throttled
return false;
}
}
public class Demo {
public static void main(String[] args) {
Middleware middleware = new AuthenticateMiddleware()
.setNextHandler(new WhitelistIpMiddleware())
.setNextHandler(new ThrottlingMiddleware(10, 1));
if (middleware.handle()) {
System.out.println("Request is valid");
} else {
System.out.println("Request is invalid");
}
}
}
Running Demo prints:
handle the functionality for throttling
Request is valid
Only the throttling step ran. setNextHandler() returns the handler it was given, so that calls can be chained. After the two chained calls, middleware holds the last handler, ThrottlingMiddleware, not the first. To run the whole chain, keep a reference to the first handler and call handle() on it:
Middleware middleware = new AuthenticateMiddleware();
middleware.setNextHandler(new WhitelistIpMiddleware())
.setNextHandler(new ThrottlingMiddleware(10, 1));
With that change, all three checks print before Request is valid.