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