设计模式第七章(责任链模式)

设计模式第七章(责任链模式)

前言

责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,其核心思想是:将多个请求处理者(对象)连接成一条 "链",当一个请求产生时,会沿着这条链依次传递,直到链中的某个处理者能够处理该请求为止

核心目的

避免请求的发送者与接收者之间形成直接耦合 ------ 发送者不需要知道具体哪个对象会处理请求,只需将请求传递给链的起始端,由链自行决定谁来处理。

关键角色

  1. 抽象处理者(Handler)

    定义一个处理请求的接口(通常包含一个处理方法),并持有一个 "下一个处理者" 的引用(即链中的下一个节点)。

  2. 具体处理者(ConcreteHandler)

    实现抽象处理者的接口,负责判断自己是否能处理当前请求:

    • 如果能处理,则直接处理;
    • 如果不能处理,则将请求传递给 "下一个处理者"。
  3. 客户端(Client)

    创建具体处理者对象,按顺序组装成责任链,并发起请求(将请求传递给链的第一个处理者)。

适用场景

  • 多个对象都可能处理同一请求,但具体由谁处理需要动态决定(如权限校验、日志记录、异常处理等);
  • 希望避免请求发送者与处理者的直接关联(解耦);
  • 需要动态调整处理者的顺序或增减处理者(如扩展功能时)。

优点

  1. 解耦:请求发送者无需知道谁会处理请求,处理者也无需知道请求的全貌;
  2. 灵活:可动态调整链中处理者的顺序或增减处理者(符合 "开闭原则");
  3. 简化对象:每个处理者只需关注自己能处理的请求,职责单一。

缺点

  1. 效率风险:如果链过长,请求可能需要经过多个处理者才能被处理,影响性能;
  2. 无人处理:如果链中所有处理者都无法处理请求,可能导致请求 "丢失"(需提前设计兜底逻辑)。

Java 中的常见应用

  • Servlet 中的 Filter 链:多个 Filter 按顺序处理请求,每个 Filter 可选择处理或传递给下一个;
  • Spring 中的拦截器(Interceptor):请求经过多个拦截器组成的链,依次执行预处理、后处理等操作。

通过责任链模式,代码会更具灵活性和可扩展性,尤其适合处理流程化、多步骤的业务场景。

责任链基本使用

​ 我们现在需要接受前端传过来的一个user对象,里面有name 和 age 属性,我们需要校验这两个属性的合法性,传统的写法就是那个用户对象,一个一个的去遍历进行校验,那么我们可以写一个注解,放到属性上面,然后调用校验方法

注解类

  • length

    java 复制代码
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Length {
    
        int value();
    }
  • Max

    java 复制代码
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Max {
    
    
        int value();
    }
  • Min

    java 复制代码
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Min {
    
    
        int value();
    }

实体类 user

java 复制代码
public class User {

    
    @Length(4)
    private String name;

    @Max(17)
    @Min(17)
    private Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

异常类

java 复制代码
public class ValidatorException extends RuntimeException{

    public ValidatorException(String message) {
        super(message);
    }
}

校验器类

java 复制代码
public class Validator {



    public void validate(Object bean) throws Exception{
        Class<?> aClass = bean.getClass();
        for (Field field : aClass.getDeclaredFields()) {
            field.setAccessible(true);
            ValidateChain chain = buildValidateChain(field);
            chain.validate(field.get(bean));
        }
    }


    /**
     *  组链的过程
     * @param field
     * @return
     */
    private ValidateChain buildValidateChain(Field field) {
        ValidateChain chain = new ValidateChain();
        Max max = field.getAnnotation(Max.class);
        if (max != null) {
            chain.addLastValidaHandler(new MaxValidateHandler(max.value()));
        }

        Min min = field.getAnnotation(Min.class);
        if (min != null) {
            chain.addLastValidaHandler(new MinValidateHandler(min.value()));
        }

        Length length = field.getAnnotation(Length.class);
        if (length != null) {
            chain.addLastValidaHandler(new LengthValidateHandler(length.value()));
        }
        return chain;
    }
}

ValidateChain 类

java 复制代码
public class ValidateChain {

    private final List<ValidateHandler> validateHandlers = new ArrayList<>();

    public void validate(Object object) {
        for (ValidateHandler handler : validateHandlers) {
            handler.validate(object);
        }
    }


    public void addLastValidaHandler(ValidateHandler handler) {
        validateHandlers.add(handler);
    }
}

校验类

java 复制代码
public interface ValidateHandler {


    void validate(Object object)throws ValidatorException;
}

public class LengthValidateHandler implements ValidateHandler{

    private final Integer length;

    public LengthValidateHandler(Integer length) {
        this.length = length;
    }

    @Override
    public void validate(Object object) throws ValidatorException {
        if (object instanceof String strValue) {
            if (strValue.length() != length) {
                throw new ValidatorException("当前值长度:"+(strValue.length()) + ",期望的值:"+ length);
            }
        }
    }
}

public class MaxValidateHandler implements ValidateHandler{

    private final Integer max;

    public MaxValidateHandler(Integer max) {
        this.max = max;
    }


    @Override
    public void validate(Object object) throws ValidatorException {
        if (object instanceof Integer intValue) {
            if (intValue > max) {
                throw new ValidatorException("当前值过大:"+intValue + ",期望的值:"+max);
            }
        }
        
public class MinValidateHandler implements ValidateHandler{

    private final Integer min;

    public MinValidateHandler(Integer min) {
        this.min = min;
    }

    @Override
    public void validate(Object object) throws ValidatorException {
        if (object instanceof Integer intValue) {
            if (intValue < min) {
                throw new ValidatorException("当前值过小:"+intValue + ",期望的值:"+min);
            }
        }
    }
}        

测试类

java 复制代码
public class Main {
    public static void main(String[] args) throws Exception {
        User user = new User("zhangsan", 18);

        Validator validator = new Validator();
        validator.validate(user);

        //当前需求是,我需要把错误的信息都返回,而不是直接中断了


    }
}

遗留问题点

  • 有的属性上面是有多个校验的,但是只能显示一个错误信息,我需要把所有的错误都显示出来,传统解决方式,在验证里面加上 try cath 。

    java 复制代码
       public void validate(Object object) {
            List<String> errList = new ArrayList<>();
            for (ValidateHandler handler : validateHandlers) {
                try {
                    handler.validate(object);
                } catch (ValidatorException e) {
                    errList.add(e.getMessage());
                }
            }
            
            if (!errList.isEmpty()) {
                throw new ValidatorException(errList.toString());
            }
        }

责任链升级

我们刚刚看到,校验我们采用的是抛异常的方式,那么我们就没有办法将一个属性的错误全部暴露出来,这个时候,我们需要有一个上下文的支持,用来存储上下文异常信息;

上下文类

java 复制代码
public class ValidatorContext {

    private final List<String> errMsgList = new ArrayList<>();

    private boolean stop = false;

    private int index = 0;


    private Object value;

    public ValidatorContext(Object value) {
        this.value = value;
    }

    public Object getValue() {
        return value;
    }

    public void appendError(String error) {
        errMsgList.add(error);
    }

    public void stopChain() {
        this.stop = true;
    }

    public boolean shouldStop() {
        return this.stop;
    }


    // 责任了继续往下走
    public void doNext(Object value) {
        index++;
        this.value = value;
    }

    public int currentIndex() {
        return index;
    }


    // 抛出错误异常
    public void throwExceptionIfNotSuccess() {
        if (!errMsgList.isEmpty()) {
            throw new ValidatorException(errMsgList.toString());
        }
    }

}

支持next才调用下一个校验

该方式类似于 servelt 中的 dofilter,每次我们使用完了后,需要继续调用一下才进行下一步,否则就不进行校验;

java 复制代码
public class ValidateChain {

    private final List<ValidateHandler> validateHandlers = new ArrayList<>();

    // 校验上下文



    public void validate(Object object) {
       ValidatorContext context = new ValidatorContext(object);
        while (true) {
            int index = context.currentIndex();
            if (index == validateHandlers.size()) {
                break;
            }
            ValidateHandler handler = validateHandlers.get(index);
            handler.validate(context.getValue(),context);
            if (index == context.currentIndex()) {
                // 说明没有调用 doNext函数
                break;
            }
        }



//        for (ValidateHandler handler : validateHandlers) {
//            handler.validate(object,context);
//            if (context.shouldStop()) {
//                break;
//            }
//        }
        context.throwExceptionIfNotSuccess();
    }


    public void addLastValidaHandler(ValidateHandler handler) {
        validateHandlers.add(handler);
    }
}
测试

我们修改MaxValidateHandler,然后在校验里面关闭 doNext值

复制代码
@Override
public void validate(Object object, ValidatorContext context) throws ValidatorException {
    if (object instanceof Integer intValue) {
        if (intValue > max) {
            context.appendError("当前值过大:"+intValue + ",期望的值:"+max);
        }
    }
    //context.doNext(object);
}

支持修改值的上下文

我们在任意步骤都可以对值进来修改,从而达到我们的目的;

复制代码
public void validate(Object object) {
   ValidatorContext context = new ValidatorContext(object);
    while (true) {
        int index = context.currentIndex();
        if (index == validateHandlers.size()) {
            break;
        }
        ValidateHandler handler = validateHandlers.get(index);
        handler.validate(context.getValue(),context);
        if (index == context.currentIndex()) {
            // 说明没有调用 doNext函数
            break;
        }
    }
测试
java 复制代码
  @Override
    public void validate(Object object, ValidatorContext context) throws ValidatorException {
        if (object instanceof Integer intValue) {
            if (intValue > max) {
                context.appendError("当前值过大:"+intValue + ",期望的值:"+max);
            }
        }
        context.doNext(20);
    }

拓展

其实我们可以在上下文里面,加入一些其他的标识,用一个map存储起来,当传入到任意的过滤器中的时候,我们都可以进行获取,从而做一些额外的操作

servlet 中的 Filter 应用

java 复制代码
  public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain)
            throws IOException, ServletException;

实际使用

我们可以在filte中增加日志的追溯id

java 复制代码
public class HttpFilter implements Filter {

    private Logger logger = LoggerFactory.getLogger(HttpFilter.class);

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse httpServletResponse = (HttpServletResponse) res;
        //跨域的header设置
        httpServletResponse.setHeader("Access-control-Allow-Origin", request.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", request.getMethod());
        httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
        try{
            if (StaticSourceFilter.isStaticSource(request.getRequestURI())) {
                chain.doFilter(req, res);
                return;
            }

            ServletRequest requestWrapper = null;
            if (req instanceof HttpServletRequest) {
                //requestWrapper = new RequestWrapperFilter((HttpServletRequest) req);
            }

            String traceId = LogUtil.getTraceId();
            ipLog(request, traceId);
            MDC.put("uuid", traceId);
            if (requestWrapper == null) {
                chain.doFilter(req, res);
            } else {
                chain.doFilter(requestWrapper, res);
            }
        }finally {
            MDC.clear();
        }

    }

总结

我们看到最终我们需要调用 chain.doFilter(requestWrapper, res); 方法进行下一步的传递操作

相关推荐
杨充19 小时前
10.可测试性实战设计
设计模式·开源·代码规范
杨充19 小时前
9.重构十二式的实战
设计模式·开源·代码规范
杨充19 小时前
6.设计原则的全景图
设计模式·开源·全栈
杨充19 小时前
2.面向对象的特性
设计模式
杨充19 小时前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充19 小时前
8.反模式与坏味道
设计模式·开源·代码规范
杨充19 小时前
3.接口vs抽象类比较
设计模式
咖啡八杯1 天前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法
咖啡八杯1 天前
GoF设计模式——解释器模式
java·后端·spring·设计模式