责任链模式是什么?
责任链模式(Chain of Responsibility Pattern)是一种行为型的设计模式,允许你把请求沿着处理者链进行传递。收到请求后,每个处理者都可以对请求进行处理,或者将其传递给链上的下一个处理者。
UML 类图
用 UML 类图来描述责任链模式的结构,在模式中各个角色之间的关系:
根据上图,总结了模式中各个角色的职责以及它们之间的关系:
- 处理者声明了具体处理者的通用接口,该接口通常仅包含单个方法用于处理请求,但有时还会包含一个设置链上下个处理者的方法。
- 基础处理者是一个可选的类,我们可以将所有处理者共用的样本代码放置在其中。通常情况下,该类中定义了一个保存对于下个处理者引用的成员变量。客户端可通过将处理者传递给上个处理者的构造函数或 setter 方法来创建链。类还可以实现默认的处理行为:确定下个处理者存在后再将请求传递给它。
- 具体处理者包含处理请求的实际代码。每个处理者接收到请求后,都必须决定是否进行处理,以及是否沿着链传递请求。
- 客户端可根据程序逻辑一次性或者动态地生成链。
案例
让我们通过一个案例来帮助我们进一步理解责任链模式。假设我们的系统希望对访问进行限制,对于接收到的请求,需要做白名单、验证码、认证等校验。
责任链模式建议我们将校验逻辑封装成一条链,链上的每个处理者除了校验请求之外,还负责沿着链传递请求。请求会在链上移动,直到所有处理者都有机会对其进行校验。更重要的是,处理者可以决定不再沿着链传递请求,这可以高效地取消所有后续校验步骤。
操作步骤
首先,我们定义一个 ClientRequest 类来表示请求对象,它包含了 IP、验证码、用户名、密码等信息。
@Data
public class ClientRequest {
private String ip;
private String captcha;
private String username;
private String password;
}
定义一个校验器接口,它声明了一个校验方法。
public interface Validator {
boolean validate(ClientRequest request);
}
创建一个基础的校验器,它有一个 Validator 类型的成员变量,保存链的下一个校验器。除此之外,基础校验器还实现了 validate 方法:确定下个校验器存在后再把请求传递给它。
public abstract class BaseValidator implements Validator {
protected Validator nextValidator;
protected BaseValidator(Validator nextValidator) {
this.nextValidator = nextValidator;
}
@Override
public boolean validate(ClientRequest request) {
if (nextValidator != null) {
return nextValidator.validate(request);
}
return true;
}
}
现在,我们可以为白名单、验证码、认证等校验逻辑定义各自具体的校验器了。以下的代码片段是白名单校验器的主要逻辑:
public class WhiteListValidator extends BaseValidator {
// ...
@Override
public boolean validate(ClientRequest request) {
// 在白名单上的 IP 才能通过校验
if (!isWhite(request.getIp())) {
System.err.println("白名单校验失败");
return false;
}
else {
System.out.println("白名单校验通过");
return super.validate(request);
}
}
private boolean isWhite(String ip) {
// ...
}
}
以下的代码片段是验证码校验器的主要逻辑:
public class CaptchaValidator extends BaseValidator {
// ...
@Override
public boolean validate(ClientRequest request) {
if (!match(request.getCaptcha())) {
System.err.println("验证码校验失败");
return false;
}
else {
System.out.println("验证码校验通过");
return super.validate(request);
}
}
private boolean match(String captcha) {
// ...
}
}
以下的代码片段是认证校验器的主要逻辑:
public class AuthValidator extends BaseValidator {
// ...
@Override
public boolean validate(ClientRequest request) {
if (!match(request.getUsername(), request.getPassword())) {
System.err.println("密码校验失败");
return false;
}
else {
System.out.println("密码校验成功");
return super.validate(request);
}
}
private boolean match(String username, String password) {
// ...
}
}
定义好这些具体的校验器之后,我们可以构建一个链串联它们。
private static Validator getValidatorChain() {
Validator authValidator = new AuthValidator(null);
Validator captchaValidator = new CaptchaValidator(authValidator);
Validator whiteListValidator = new WhiteListValidator(captchaValidator);
return whiteListValidator;
}
最后,我们通过一个用例来演示如何把请求传递给校验器链进行校验。
public static void main(String[] args) {
Validator validatorChain = getValidatorChain();
ClientRequest clientRequest = new ClientRequest();
clientRequest.setIp("127.0.0.1");
clientRequest.setCaptcha("1qaz");
clientRequest.setUsername("huey");
clientRequest.setPassword("1qaz2wsx");
boolean validated = validatorChain.validate(clientRequest);
if (validated) {
System.out.println("请求通过校验");
}
else {
System.err.println("请求没有通过校验");
}
}
案例源码
可在 GitHub 上查看上述案例的完整代码。
优点 & 缺点
使用责任链有如下的好处:
- 遵循单一职责原则,可以对发起的操作和执行操作的类进行解耦
- 遵循开闭原则,可以在不更改现有代码的情况下在程序中新增处理者
但责任链也存在不足之处:
- 如果处理者没有正确地传递请求,那么请求可能会丢失。
- 如果处理者把请求传递给之前的处理者,那么链就会变成环,造成循环
- 责任链可能会深度的堆栈跟踪,影响性能
-
JDK 中的责任链
调用链在 Java 中最典型的案例是 Servlet 的 Filter,它允许我们定义多个 Filter 去处理 HTTP 请求。 ```java public class CustomFilter implements Filter {
public void doFilter( ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// process the request
// pass the request (i.e. the command) along the filter chain
chain.doFilter(request, response);
}
} ```
适用场景
以下场景可以考虑使用责任链模式:
- 有多个对象处理同一个请求,具体由哪一个来处理还不确定,只有在运行时才能确定哪个对象来处理的情况。
- 同一个请求的多个处理对象可能会动态地增加或减少,需要动态指定的情况。
参考资料
本文参考了以下资料: