Introduction
The Chain of Responsibility pattern is a design pattern that allows a series of objects to handle a request. In this pattern, each object in the chain has a chance to handle the request before passing it on to the next object. If none of the objects in the chain can handle the request, the request is handled by a default object or rejected altogether.
The Chain of Responsibility pattern consists of two main components: the Handler and the Client. The Handler is an abstract class or interface that defines a method for handling requests and a reference to the next Handler in the chain. The Client is the object that makes the request and is responsible for initiating the chain.
UML Diagram
+--------------+ +--------------+
| Handler | | Client |
+--------------+ +--------------+
| -nextHandler | | + sendReq() |
+--------------+ +--------------+
^ |
| |
| +---------------+
| | Concrete |
| | Handler |
| +---------------+
| | + handleReq() |
| +---------------+
| ^
| |
| +---------------+
| | Concrete |
| | Handler |
| +---------------+
| | + handleReq() |
| +---------------+
| ^
| |
| +---------------+
| | Concrete |
| | Handler |
| +---------------+
| | + handleReq() |
| +---------------+
Benefits
Reduced Coupling: The Chain of Responsibility pattern allows developers to separate concerns between objects, making it easier to modify and maintain code.
Scalability: The Chain of Responsibility pattern is designed to handle complex problems, making it easier to scale applications as they grow.
Flexibility: The Chain of Responsibility pattern allows developers to add or remove Handlers at runtime, providing greater flexibility in choosing the appropriate Handler for a particular situation.
Examples
Example 1: Purchase Approval Suppose we want to build a purchase approval system that allows employees to submit purchase requests for approval. In this case, the purchase requests act as the Client, and the approval process consists of a chain of Handlers. The Handlers include the Employee, the Manager, and the CEO. Each Handler has a maximum approval limit, and if the purchase request exceeds the limit, the request is passed on to the next Handler in the chain.
Example 2: Authentication System Suppose we want to build an authentication system that allows users to log in to an application. In this case, the authentication requests act as the Client, and the authentication process consists of a chain of Handlers. The Handlers include the Username Handler, the Password Handler, and the Two-Factor Authentication Handler. Each Handler checks a different aspect of the user's authentication credentials, and if any Handler fails, the authentication request is rejected.
Example 3: Customer Support System Suppose we want to build a customer support system that allows customers to submit support requests. In this case, the support requests act as the Client, and the support process consists of a chain of Handlers. The Handlers include the Level 1 Support Handler, the Level 2 Support Handler, and the Level 3 Support Handler. Each Handler is responsible for handling support requests of increasing complexity, and if a Handler is unable to handle a request, it is passed on to the next Handler in the chain.
Implementation
public abstract class AuthenticationProcessor {
public AuthenticationProcessor nextProcessor;
// standard constructors
public abstract boolean isAuthorized(AuthenticationProvider authProvider);
}
public class OAuthProcessor extends AuthenticationProcessor {
public OAuthProcessor(AuthenticationProcessor nextProcessor) {
super(nextProcessor);
}
@Override
public boolean isAuthorized(AuthenticationProvider authProvider) {
if (authProvider instanceof OAuthTokenProvider) {
return true;
} else if (nextProcessor != null) {
return nextProcessor.isAuthorized(authProvider);
}
return false;
}
}
public class UsernamePasswordProcessor extends AuthenticationProcessor {
public UsernamePasswordProcessor(AuthenticationProcessor nextProcessor) {
super(nextProcessor);
}
@Override
public boolean isAuthorized(AuthenticationProvider authProvider) {
if (authProvider instanceof UsernamePasswordProvider) {
return true;
} else if (nextProcessor != null) {
return nextProcessor.isAuthorized(authProvider);
}
return false;
}
}
public class ChainOfResponsibilityTest {
private static AuthenticationProcessor getChainOfAuthProcessor() {
AuthenticationProcessor oAuthProcessor = new OAuthProcessor(null);
return new UsernamePasswordProcessor(oAuthProcessor);
}
@Test
public void givenOAuthProvider_whenCheckingAuthorized_thenSuccess() {
AuthenticationProcessor authProcessorChain = getChainOfAuthProcessor();
assertTrue(authProcessorChain.isAuthorized(new OAuthTokenProvider()));
}
@Test
public void givenSamlProvider_whenCheckingAuthorized_thenSuccess() {
AuthenticationProcessor authProcessorChain = getChainOfAuthProcessor();
assertFalse(authProcessorChain.isAuthorized(new SamlTokenProvider()));
}
}
Request Validation Detailed Example
Source: https://refactoring.guru/design-patterns/chain-of-responsibility/java/example
Base Middleware Class
/ Base middleware class. / public abstract class Middleware { private Middleware next; / Builds chains of middleware objects. / public static Middleware link(Middleware first, Middleware... chain) { Middleware head = first; for (Middleware nextInChain: chain) { head.next = nextInChain; head = nextInChain; } return first; } / Subclasses will implement this method with concrete checks. / public abstract boolean check(String email, String password); / Runs check on the next object in chain or ends traversing if we're in last object in chain. / protected boolean checkNext(String email, String password) { if (next == null) { return true; } return next.check(email, password); } }
ThrottlingMiddleware Class
/ ConcreteHandler. Checks whether there are too many failed login requests. / public class ThrottlingMiddleware extends Middleware { private int requestPerMinute; private int request; private long currentTime; public ThrottlingMiddleware(int requestPerMinute) { this.requestPerMinute = requestPerMinute; this.currentTime = System.currentTimeMillis(); } / Please, not that checkNext() call can be inserted both in the beginning of this method and in the end. This gives much more flexibility than a simple loop over all middleware objects. For instance, an element of a chain can change the order of checks by running its check after all other checks. / public boolean check(String email, String password) { if (System.currentTimeMillis() > currentTime + 60_000) { request = 0; currentTime = System.currentTimeMillis(); } request++; if (request > requestPerMinute) { System.out.println("Request limit exceeded!"); Thread.currentThread().stop(); } return checkNext(email, password); } }
UserExistsMiddleware Class
/ ConcreteHandler. Checks whether a user with the given credentials exists. / public class UserExistsMiddleware extends Middleware { private Server server; public UserExistsMiddleware(Server server) { this.server = server; } public boolean check(String email, String password) { if (!server.hasEmail(email)) { System.out.println("This email is not registered!"); return false; } if (!server.isValidPassword(email, password)) { System.out.println("Wrong password!"); return false; } return checkNext(email, password); } }
RoleCheckMiddleware Class
/ ConcreteHandler. Checks a user's role. / public class RoleCheckMiddleware extends Middleware { public boolean check(String email, String password) { if (email.equals("admin@example.com")) { System.out.println("Hello, admin!"); return true; } System.out.println("Hello, user!"); return checkNext(email, password); } }
Server Class
/ Server class. / public class Server { private Map users = new HashMap<>(); private Middleware middleware; / Client passes a chain of object to server. This improves flexibility and makes testing the server class easier. / public void setMiddleware(Middleware middleware) { this.middleware = middleware; } / Server gets email and password from client and sends the authorization request to the chain. / public boolean logIn(String email, String password) { if (middleware.check(email, password)) { System.out.println("Authorization have been successful!"); // Do something useful here for authorized users. return true; } return false; } public void register(String email, String password) { users.put(email, password); } public boolean hasEmail(String email) { return users.containsKey(email); } public boolean isValidPassword(String email, String password) { return users.get(email).equals(password); } }
Client Code to Demonstrate
/** Demo class. Everything comes together here. / public class Demo { private static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); private static Server server; private static void init() { server = new Server(); server.register("admin@example.com", "admin_pass"); server.register("user@example.com", "user_pass"); // All checks are linked. Client can build various chains using the same // components. Middleware middleware = Middleware.link( new ThrottlingMiddleware(2), new UserExistsMiddleware(server), new RoleCheckMiddleware() ); // Server gets a chain from client code. server.setMiddleware(middleware); } public static void main(String[] args) throws IOException { init(); boolean success; do { System.out.print("Enter email: "); String email = reader.readLine(); System.out.print("Input password: "); String password = reader.readLine(); success = server.logIn(email, password); } while (!success); } }
Top comments (0)