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

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

前言

责任链模式(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); 方法进行下一步的传递操作

相关推荐
Deschen3 小时前
设计模式-原型模式
java·设计模式·原型模式
☆cwlulu3 小时前
c++最常用的几种设计模式
设计模式
天才测试猿13 小时前
WebUI自动化测试:POM设计模式全解析
自动化测试·软件测试·python·selenium·测试工具·设计模式·测试用例
NiKo_W14 小时前
Linux 进程通信——基于责任链模式的消息队列
linux·服务器·消息队列·责任链模式·进程通信
Asort14 小时前
JavaScript设计模式(十三)——责任链模式:构建灵活高效的请求处理链
前端·javascript·设计模式
笨手笨脚の14 小时前
设计模式-访问者模式
设计模式·访问者模式·行为型设计模式
bkspiderx14 小时前
C++设计模式之行为型模式:模板方法模式(Template Method)
c++·设计模式·模板方法模式
o0向阳而生0o14 小时前
108、23种设计模式之模板方法模式(17/23)
设计模式·模板方法模式
canonical_entropy17 小时前
组合为什么优于继承:从工程实践到数学本质
后端·数学·设计模式