Redian新闻
>
巧用『责任链模式』来优化 参数多重校验,非常优雅!

巧用『责任链模式』来优化 参数多重校验,非常优雅!

公众号新闻

点击上方“芋道源码”,选择“设为星标

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

 
来源:juejin.cn/post/
7011490664714240008

背景

最近在做需求,写一个方法,先在前面做验证,if 不满足 A 条件则 return,if 不满足 B 条件则 return...一共写了 5 个验证,等验证通过以后才执行下面的逻辑,这个也没问题。过了一阵产品提了需求,跟这个方法类似,我又把这个方法 copy 了一份,只不过验证条件稍微有点不一样,要变成 6 个验证了。

这时候我就发现有三个问题,第一重复代码,相同的 A 条件 B 条件 C 条件写了两份,没有复用。第二,“头重脚轻”,比如 100 行的方法,前面 60 行都是验证,后面 40 行才是真正有用的业务代码,你看一个方法功能的时候前面验证肯定是不关心的,只看后面 40 行到底在干什么逻辑,所以要缩短验证代码的行数。第三,先后顺序,一个方法A是在B之前验证的,另一个方法 A 是在 B 之后验证的,调整起来很不方便。

这时候我就想到了用「责任链模式」 来进行优化解决。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

定义

责任链模式(Chain of Responsibility Pattern)是将链中每一个节点看作是一个对象,每个节点处理的请求均不同,且内部自动维护一个下一节点对象。当一个请求从链式的首端发出时,会沿着链的路径依次传递给每一个节点对象,直至有对象处理这个请求为止。属于行为型模式。

生活中的应用场景就是「审批流」 。责任链模式主要是解耦了请求与处理,客户只需将请求发送到链上即可,无需关心请求的具体内容和处理细节,请求会自动进行传递直至有节点对象进行处理。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

通用UML类图

责任链模式

例子

下面写一个登录验证判断的例子,一般责任链模式会搭配着「建造者模式」 一起用,即「链式编程」 。因为这样链条看起来更加清晰明了,而传统的写法很抽象,很难看出谁谁谁在谁的前面,谁谁谁在谁的后面,如下所示:

AAAHandler.setNextHandler(deptManagerLeaveHandler);
directLeaderLeaveHandler.setNextHandler(deptManagerLeaveHandler);
BBBHandler.setNextHandler(AAAHandler);
deptManagerLeaveHandler.setNextHandler(gManagerLeaveHandler);

下面先创建一个 Handler 的抽象类,这个类里面有一个下一个 Handler 处理器 next,还有一个 Builder,这个就是用来构建链的,也是方便我们的链式编程。

public abstract class Handler<T{

    protected Handler next;

    private void next(Handler next) {
        this.next = next;
    }

    public abstract void doHandler(Member member);

    public static class Builder<T{
        private Handler<T> head;
        private Handler<T> tail;

        public Builder<T> addHandler(Handler handler) {
            if (this.head == null) {
                this.head = this.tail = handler;
                return this;
            }
            this.tail.next(handler);
            this.tail = handler;
            return this;
        }

        public Handler<T> build() {
            return this.head;
        }
    }
}

下面写非空校验 ValidateHandler 类,这里面先判断用户名和密码是否为空,空的话返回,非空的话判断 next 是否为空,非空的话就丢给下一个处理器去执行。

public class ValidateHandler extends Handler {
    @Override
    public void doHandler(Member member) {
        if (StringUtils.isEmpty(member.getUsername()) ||
                StringUtils.isEmpty(member.getPassword())) {
            System.out.println("用户名和密码不能为空");
            return;
        }
        if (null != next) {
            next.doHandler(member);
        }
    }
}

创建登录检验LoginHandler类,判断账号密码是否正确

public class LoginHandler extends Handler {

    @Override
    public void doHandler(Member member) {
        if (!"jack".equals(member.getUsername()) || !"666".equals(member.getPassword())) {
            System.out.println("用户名密码不正确");
            return;
        }
        if (null != next) {
            next.doHandler(member);
        }
    }
}

创建权限检验 AuthHandler 类,判断角色是否有权限

public class AuthHandler extends Handler {
    @Override
    public void doHandler(Member member) {
        if (!"管理员".equals(member.getRoleName())) {
            System.out.println("您不是管理员,没有操作权限");
            return;
        }
        if (null != next) {
            next.doHandler(member);
        }
    }
}

创建执行业务逻辑类

public class BusinessLogicHandler extends Handler {

    @Override
    public void doHandler(Member member) {
        System.out.println("执行业务逻辑。。");
    }
}

好,下面写个测试类来测试一下

public class Test {

    public static void main(String[] args) {
        Handler.Builder builder = new Handler.Builder();
        //这里就是链式编程,谁在前谁在后看的清清楚楚,明明白白
        builder.addHandler(new ValidateHandler())
                .addHandler(new LoginHandler())
                .addHandler(new AuthHandler())
                .addHandler(new BusinessLogicHandler());
        Member member = new Member();
        member.setUsername("");
        member.setPassword("");
        builder.build().doHandler(member);
    }

}

执行一下,提示用户名密码不能为空

修改下用户名和密码

执行一下,提示用户名密码不正确

直到把用户名密码权限都设置正确

此时所有验证都通过,开始执行业务逻辑了

源码中的应用

我们来看一个J2EE标准中非常常见的Filter类:

public interface Filter {
    public default void init(FilterConfig filterConfig) throws ServletException {}

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain)
 throws IOException, ServletException
;

    public default void destroy() {}
}

这个Filter接口的定义非常简单,相当于责任链模型中的handler抽象角色,我们来看Spring中的实现MockFilterChain类:

public class MockFilterChain implements FilterChain {

   @Nullable
   private ServletRequest request;
   @Nullable
   private ServletResponse response;
   private final List<Filter> filters;
   @Nullable
   private Iterator<Filter> iterator;

   public MockFilterChain() {
      this.filters = Collections.emptyList();
   }

   public MockFilterChain(Servlet servlet) {
      this.filters = initFilterList(servlet);
   }

   public MockFilterChain(Servlet servlet, Filter... filters) {
      Assert.notNull(filters, "filters cannot be null");
      Assert.noNullElements(filters, "filters cannot contain null values");
      this.filters = initFilterList(servlet, filters);
   }

   private static List<Filter> initFilterList(Servlet servlet, Filter... filters) {
      Filter[] allFilters = ObjectUtils.addObjectToArray(filters, new ServletFilterProxy(servlet));
      return Arrays.asList(allFilters);
   }

   @Nullable
   public ServletRequest getRequest() {
      return this.request;
   }

   @Nullable
   public ServletResponse getResponse() {
      return this.response;
   }

   @Override
   public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
      Assert.notNull(request, "Request must not be null");
      Assert.notNull(response, "Response must not be null");
      Assert.state(this.request == null"This FilterChain has already been called!");

      if (this.iterator == null) {
         this.iterator = this.filters.iterator();
      }
      //核心代码执行
      if (this.iterator.hasNext()) {
         Filter nextFilter = this.iterator.next();
         nextFilter.doFilter(request, response, this);
      }

      this.request = request;
      this.response = response;
   }

   public void reset() {
      this.request = null;
      this.response = null;
      this.iterator = null;
   }

       ...
}

这里面把链条中所有的 Filter 都放到List<Filter> filters中, 在 doFilter() 方法中有一段核心的代码this.iterator.hasNext(),这个就相当于 for 循环的执行 filters 中的 Filter 方法。「虽然写法不同,但也起到了责任链的功能,所以在学习设计模式中,不要拘泥于标准的写法,很多都是变种的,或者写着写着四不像的模式,既像这个设计模式,又像那个设计模式,这个很正常,能起到精简代码,高效运行的都是好代码。」

优缺点

优点:

  1. 将请求与处理解耦。
  2. 请求处理者(节点对象)只需关注自己感兴趣的请求进行处理,对于不感兴趣的请求,直接转发给下一级节点对象。
  3. 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果。
  4. 链路结构灵活,可以通过改变链路结构动态地新增或删减责任。
  5. 易于扩展新的请求处理类(节点),符合开闭原则。

缺点:

  1. 责任链太长或者处理时间过长,会影响整体性能。
  2. 如果节点对象存在循环引用时,会造成死循环,导致系统崩溃。


欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。

谢谢支持哟 (*^__^*)

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
钱学森的“回国观感”Java统计用户在线人数,这样做才优雅!NeurlPS 2022 | 全新大模型参数高效微调方法:仅需训练0.3M的参数好看!爸爸巧用妻子姓名印章为儿子画像……听,教育早新闻来啦!从11s到170ms!看看人家的接口优化技巧,那叫一个优雅!习大帝也不是一无是处,治好了朋友的抑郁Meta发布首个「非参数化」掩码语言模型NPM:吊打500倍参数量的GPT-3瞧瞧别人家的 API 接口,那叫一个优雅!在美国271.害人害己某顶级事务所的LUMION特效参数流出?网友:出图就这么简单?(附参数下载)巧用水纹玻璃,规避风水问题,打造和谐的家 | 虫点子新作【居住榜样】(视频)盗贼团伙巧用骗术从南加珠宝店偷走戒指,价值$4万女孩被双重校园霸凌后自杀,校方却不闻不问,反向女孩父亲泼脏水面试官问我什么是责任链模式,我把这篇文章甩给了他2023年,抖音全链路种草用『三明治』方法暴雨渗透,洛杉矶中餐馆巧用300磅大米自救尿尿次数多,说明肾不好?直播专家告诉你别再用 if 校验参数了,太Low!这才是专业的 SpringBoot 参数校验方式!艳遇52岁“性感奶奶”凭一双美腿爆红网络:原来老去也可以这么优雅!3000一张的Lumion图,原来只是套了个参数!(附参数下载)瞧瞧人家写的API接口代码,那叫一个优雅!西藏突发:8人遇难!最震撼年终奖:6000万堆成山,最高一人抱走500万!更有数钱比赛,数多少拿多少!十大预测错九个?回应来了NeurlPS 2022 | 全新大模型参数高效微调方法SSF:仅需训练0.3M的参数,效果卓越Controller 层代码就该这么写,简洁又优雅!过年让你看起来优雅高级又贵气的秘诀在这里!买到疯狂也值得!69岁赵雅芝穿露肩红裙 身材火辣气质优雅!陈奕迅演唱会唱一半无奈道歉了【广而告之】『异乡好居返现召集令』来啦!各位UIUC的同学快去提现啦!克罗地亚加入申根区,取得“欧洲绿卡”原来优势那么多吃起来像果冻,喝起来是果粒橙,可以“吸”的『爱媛果冻橙』来了,皮超薄,肉细腻,一口就爆汁。走资派邓小平窃国大盗乱世奸雄冬天也要优雅!SOIA&KYO冬季羽绒服、大衣低至5折!从阿里跳槽来的工程师,写个try catch的方式都这么优雅!从阿里跳槽来的工程师,写个Controller都这么优雅!
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。