设计模式-责任链模式
职责链模式的英文翻译是 Chain Of Responsibility Design Pattern。它是这么定义的:Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it. 翻译成中文就是:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止
。
注意在这个定义中,直到某个接收对象能够处理它为止,也就是某个对象如果可以处理,就到此中止了,不再向下执行。
在策略模式中提到,定义一组策略,使它们可以互相替换,在运行时动态确定要执行的策略。如果从这个角度看,其实策略模式和责任链模式是比较类似的,都是找到某个适合处理它的策略。不过策略模式强调动态确定以及可以互相替换,而责任链的变种实际上更加常用,即单个对象处理后不立刻结束,直到链上的所有对象处理完成后再结束。
责任链的这两种实现对应不同,例如使用责任链进行拦截,其中某一个校验不通过已经可以返回401或403之类的了,如果处理器链上的某个处理器能够处理这个请求,那就不会继续往下传递请求。这个例子不太恰当,所有的链节点都可以处理,只是相当于在第一个处理不通过的时候返回了,需要结合一些条件才能完美符合原始定义。假如使用责任链进行关键字过滤,例如一个处理涉--、一个处理涉--等,这就要求所有的节点都进行处理,例如 Spring 中的 Filter 设计。
案例分析
责任链模式实现 JPA 中的 validation 注解校验,JPA 中@MAX 可以限制该字段的最大值,@MIN 可以限制该字段的最小值,该案例就是实现一个这样的效果
首先定义相关注解
java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Length {
int value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Max {
int value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Min {
int value();
}
老演员 user 类
java
@Data
public class User {
@Length(4)
private String name;
@Max(10)
@Min(18)
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
校验接口
java
public interface Validator {
void validate(Field field, Object value, ValidatorContext context) throws IllegalAccessException;
}
三个注解对应的校验实现类
java
public class LengthVerifier implements Validator {
@Override
public void validate(Field field, Object value, ValidatorContext context) {
Length length = field.getAnnotation(Length.class);
if (length != null) {
int len = length.value();
if (value instanceof String) {
String v = (String) value;
if (v.length() != len) {
context.appendErrMsg(field.getName() + "当前值为:" + v + ",与要求长度:" + len + "不一致");
}
}
}
}
}
public class MaxVerifier implements Validator {
@Override
public void validate(Field field, Object value, ValidatorContext context) throws IllegalAccessException {
Max max = field.getAnnotation(Max.class);
if (max != null) {
int maxValue = max.value();
if (value instanceof Integer) {
int v = (Integer) value;
if (v > maxValue) {
context.appendErrMsg(field.getName() + "当前值为:" + v + ",大于最大值:" + maxValue);
// context.stop();
// Object bean = context.getBean();
// field.setInt(bean, 20);
}
}
}
}
}
public class MinVerifier implements Validator {
@Override
public void validate(Field field, Object value, ValidatorContext context) {
Min min = field.getAnnotation(Min.class);
if (min != null) {
int minValue = min.value();
if (value instanceof Integer) {
int v = (Integer) value;
if (v < minValue) {
context.appendErrMsg(field.getName() + "当前值为:" + v + ",小于最小值:" + minValue);
}
}
}
}
}
真正的校验类
java
public class Checker {
// 可以利用 Spring 自动注入,但是需要注意校验器顺序
private List<Validator> validatorList = new ArrayList<>();
public void addValidator(Validator validator) {
validatorList.add(validator);
}
public void validate(Object bean) throws IllegalAccessException {
ValidatorContext context = new ValidatorContext(bean);
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.getAnnotations().length == 0) {
continue;
}
field.setAccessible(true);
for (Validator validator : validatorList) {
if (context.getStop()) {
break;
}
validator.validate(field, field.get(bean), context);
}
}
context.throwErrMsgIfNecessary();
}
}
校验器上下文类
java
public class ValidatorContext {
private List<String> errMsgs;
private Object bean;
private boolean stop;
public ValidatorContext(Object bean) {
this.bean = bean;
this.stop = false;
this.errMsgs = new ArrayList<>();
}
public void appendErrMsg(String msg) {
errMsgs.add(msg);
}
public String getErrMsg() {
return errMsgs.toString();
}
public void stop() {
stop = true;
}
public boolean getStop() {
return this.stop;
}
public Object getBean() {
return this.bean;
}
public void throwErrMsgIfNecessary() {
if (errMsgs.size() > 0) {
throw new ValidateException(errMsgs.toString());
}
}
}
首先解释为什么需要有校验器上下文类:
按照责任链标准定义,例如 user 的 age 字段,我们要求介于 min 和 max 之间,那么其实一个不满足就可以抛出校验失败的异常了,这就不需要上下文类了。
但是如果要求所有的校验器都完成后再返回校验结果,那么需要把上一个校验器的结果传递给下一个校验器,因此引入 校验器上下文类 引用传递,这样就可以校验完成后统一处理。
如果你了解 Spring 的 Filter 机制,我们在实现 Filter 时需要在最后调用 filterChain.doFilter(servletRequest, servletResponse); 方法,如果不调用就会中止在这里
java
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 省略
filterChain.doFilter(servletRequest, servletResponse);
}
这里的 FilterChain 就相当于这些链的上下文,利用属性 index 记录了当前执行到哪个链了,如果不显式调用 filterChain.doFilter(servletRequest, servletResponse),该链就会中止。
测试代码:
java
public class Main {
public static void main(String[] args) throws IllegalAccessException {
User user = new User("zhan", 12);
Checker validate = new Checker();
validate.addValidator(new MaxVerifier());
validate.addValidator(new MinVerifier());
validate.addValidator(new LengthVerifier());
validate.validate(user);
}
}
更简单的实现可以在上下文中定义一个 stop 属性,每次执行前先判断 stop 是否为 true
引入上下文还有好处就是可以存当前的值,可以在最后赋值给原属性,例如过滤器,A过滤器将某些非法字符转为--,B过滤器可能将其他字段转为--,最终要的其实是所有过滤器处理后的值,这个值可以从上下文中获取和存储,并在最后赋值给原始对象。
总结
责任链的不同实现之间一般可以关注两大方面:
- 某个链节点处理完成后是否还传递给下一个节点处理
- 存储链节点的方式(链表、数组)等
在使用责任链设计模式时,一定要关注责任链中各个节点的处理顺序,可以通过手动添加、Order 注解等方式来加以控制。