设计模式-责任链模式

设计模式-责任链模式

职责链模式的英文翻译是 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 注解等方式来加以控制。

相关推荐
笨手笨脚の5 小时前
设计模式-策略模式
设计模式·策略模式·行为型设计模式
王嘉俊9256 小时前
设计模式--适配器模式:优雅解决接口不兼容问题
java·设计模式·适配器模式
王嘉俊9256 小时前
设计模式--组合模式:统一处理树形结构的优雅设计
java·设计模式·组合模式
rongqing20196 小时前
Google 智能体设计模式:多智能体协作
设计模式
李广坤21 小时前
状态模式(State Pattern)
设计模式
李广坤1 天前
观察者模式(Observer Pattern)
设计模式
李广坤1 天前
中介者模式(Mediator Pattern)
设计模式
李广坤1 天前
迭代器模式(Iterator Pattern)
设计模式
李广坤1 天前
解释器模式(Interpreter Pattern)
设计模式