草庐IT

设计模式之责任链模式

程序员田同学 2023-03-28 原文

本文通过图书馆管理系统中,用户名校验、密码校验、需要增加问题,每次都要增加if判断语句,将其改用责任链模式进行链式调用,为了让代码更加的优雅,我们使用之前学过的建造者模式就代码进行改造。接着我们会介绍责任链模式在我们常用的框架中的运用,最后是责任链模式的优缺点和应用场景。

读者可以拉取完整代码到本地进行学习,实现代码均测试通过后上传到码云

一、引出问题

小王给老王打造了一套图书馆管理系统,随着访问量的不断增加,老王要求增加访问的用户名校验。

小王说这有何难,说着就在用户访问图书馆之前加了一层判断语句,判断用户名是否合法。过了一段时间后,又给每个用户颁发了一个密码,就需要在用户名校验通过以后校验密码。

小王就准备在用户名的判断语句后,增加密码的校验语句。老王赶忙拦住了要继续更改代码的小王。如果以后再增加角色校验、权限校验、你准备写多少个判断语句。

而且你把软件设计原则中的——开闭原则丢到哪里去了。

你可以考虑使用一种模式,将所有的校验方法都独立出来一个类,每一个类只负责处理各自的校验逻辑,当前的校验类通过以后传递给下一个校验类进行处理,这样每次增加新的逻辑判断都只需要增加校验类就行了。

就像是一条流水线,每个类负责处理线上的一个环节。

二、责任链模式的概念和使用

实际上,老王提出来的正是行为型设计模式中的——**责任链模式

责任链模式正如它的名字一样,将每个职责的步骤串联起来执行,并且一个步骤执行完成之后才能够执行下一个步骤。

从名字可以看出通常责任链模式使用链表来完成。 因此当执行任务的请求发起时,从责任链上第一步开始往下传递,直到最后一个步骤完成。

在责任链模式当中, 客户端只用执行一次流程开始的请求便不再需要参与到流程执行当中,责任链上的流程便能够自己一直往下执行,

客户端同样也并不关心执行流程细节,从而实现与流程之间的解耦。

责任链模式需要有两个角色:

抽象处理器(Handler):处理器抽象接口,定义了处理请求的方法和执行下一步处理的处理器。

具体处理器(ConcreteHandler):执行请求的具体实现,先根据请求执行处理逻辑,完成之后将请求交给下一个处理器执行。

基于责任链模式实现图书馆的用户名校验和密码校验。

抽象处理器:

/**
 * @author tcy
 * @Date 22-08-2022
 */
public abstract class Handler {

    private Handler next;

    public Handler getNext() {
        return next;
    }

    public void setNext(Handler next) {
        this.next = next;
    }

    public abstract void handle(Object request);


}

用户名校验处理器:

/**
 * @author tcy
 * @Date 23-08-2022
 */
public class ConcreteHandlerUsername extends Handler{
    @Override
    public void handle(Object request) {

        //相应的业务逻辑...
        System.out.println("用户名校验通过. 参数: " + request);

        //调用链路中下一个节点的处理方法
        if (getNext() != null) {

            getNext().handle(request);
        }

    }
}

密码校验器:

/**
 * @author tcy
 * @Date 23-08-2022
 */
public class ConcreteHandlerPassword extends Handler{
    @Override
    public void handle(Object request) {

        //相应的业务逻辑...

        System.out.println("密码校验通过. 参数: " + request);

        //调用链路中下一个节点的处理方法
        if (getNext() != null){

            getNext().handle(request);
        }

    }
}

客户端调用:

public class Client {

    //普通模式----------
    public static void main(String[] args) {
        Handler concreteHandler1 = new ConcreteHandlerUsername();
        Handler concreteHandler2 = new ConcreteHandlerPassword();
    
       concreteHandler1.setNext(concreteHandler2);
    
       concreteHandler1.handle("用户名tcy");
    
    }
    }
    
  用户名校验通过. 参数: 用户名tcy
  密码校验通过. 参数: 用户名tcy

这样我们就实现了责任链模式,但是这种方式我们注意到,调用方调用的时候手动将两个处理器set到一起,如果这条链路很长的时候,这样的代码实在是太不优雅了。

将我们曾经学过的设计模式扒出来,看使用哪种模式能让它看起来更优雅一点。

三、责任链模式+建造者模式

我们看建造型设计模式的文章,看建造者模式中的典型应用中的Lombok。

参考Lombok的 @Builder例子,是不是和我们这个有着些许相似呢?

我们在Handle的类中创建一个Builder内部类。

/**
 * 建造者模式
 */
public static class Builder{
    private Handler head;
    private Handler tail;

    public Builder addHanlder(Handler handler){
        //head==null表示第一次添加到队列
        if (null == head){
            head = this.tail = handler;
            return this;
        }
        //原tail节点指向新添加进来的节点
        this.tail.setNext(handler);
        //新添加进来的节点设置为tail节点
        this.tail = handler;
        return this;
    }

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

该内部类更像是一个链表结构,定义一个头和尾对象,add方法是向链接的头尾中赋值,build返回头元素方便开始链式调用。我们对调用方代码进行改造。

//建造者模式---------
public static void main(String[] args) {
    Handler.Builder builder = new Handler.Builder();
    builder.addHanlder(new ConcreteHandlerUsername())
            .addHanlder(new ConcreteHandlerPassword());
    builder.build().handle("用户名tcy");

}

这样的实现方式比原方式优雅多了。责任链模式本身是很简单的,如果将责任链模式和建造者模式组合起来使用就没那么容易理解了。

在实际使用中往往不是一个单一的设计模式,更多的是多种组合模式组成的“四不像”,实际上这并不是一件轻松的事。

四、责任链模式在源码运用

为了加深理解我们继续深入责任链模式在Spring中的运用。

Spring Web 中的 HandlerInterceptor,里面有preHandle()postHandle()afterCompletion()三个方法,实现这三个方法可以分别在调用"Controller"方法之前,调用"Controller"方法之后渲染"ModelAndView"之前,以及渲染"ModelAndView"之后执行。

public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        return true;
    }

    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable ModelAndView modelAndView) throws Exception {
    }

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable Exception ex) throws Exception {
    }

}

HandlerInterceptor就是角色中的抽象处理者,HandlerExecutionChain相当于上述中的Client,用于调用责任链上的各个环节。

public class HandlerExecutionChain {
...

@Nullable
private HandlerInterceptor[] interceptors;

private int interceptorIndex = -1;

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = 0; i < interceptors.length; i++) {
            HandlerInterceptor interceptor = interceptors[i];
            if (!interceptor.preHandle(request, response, this.handler)) {
                triggerAfterCompletion(request, response, null);
                return false;
            }
            this.interceptorIndex = i;
        }
    }
    return true;
}

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
        throws Exception {

    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = interceptors.length - 1; i >= 0; i--) {
            HandlerInterceptor interceptor = interceptors[i];
            interceptor.postHandle(request, response, this.handler, mv);
        }
    }
}

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
        throws Exception {

    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = this.interceptorIndex; i >= 0; i--) {
            HandlerInterceptor interceptor = interceptors[i];
            try {
                interceptor.afterCompletion(request, response, this.handler, ex);
            }
            catch (Throwable ex2) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
            }
        }
    }
}
}

私有的数组 private HandlerInterceptor[] interceptors 用于存储责任链的每个环节,,然后通过interceptorIndex作为指针去遍历责任链数组按顺序调用处理者。

结合我们上面给出的例子,在Spring中的应用是比较容易理解的。

在Servlet的一系列拦截器也是采用的责任链模式,有兴趣的读者可以深入研究一下。

五、总结

当必须按顺序执行多个处理者时,可以考虑使用责任链模式。如果处理者的顺序及其必须在运行时改变时,可以考虑使用责任链模式。责任链的模式是缺点也很明显,增加了系统的复杂性。

但是要切忌避免过度设计,在实际应用中,校验用户名和密码的业务逻辑并没有那么的复杂,可能只是一个判断语句,使用设计模式只会增加系统的复杂性,而在Shiro、SpringSecurity、SpringMVC的拦截器中使用责任链模式是一个好的选择。

如果在你的项目业务中需要定义一系列拦截器,那么使用责任链模式就是一个比较不错的选择。

我已经连续更新了数十篇设计模式博客,推荐你结合学习。

一、设计模式概述

二、设计模式之工厂方法和抽象工厂

三、设计模式之单例和原型

四、设计模式之建造者模式

五、设计模式之代理模式

六、设计模式之适配器模式

七、设计模式之桥接模式

八、设计模式之组合模式

九、设计模式之装饰器模式

十、设计模式之外观模式

十一、外观模式之享元模式

有关设计模式之责任链模式的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  4. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  5. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  6. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  7. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  8. ruby-on-rails - environment.rb 中设置的常量在开发模式中消失 - 2

    了解Rails缓存如何工作的人可以真正帮助我。这是嵌套在Rails::Initializer.runblock中的代码:config.after_initializedoSomeClass.const_set'SOME_CONST','SOME_VAL'end现在,如果我运行script/server并发出请求,一切都很好。然而,在我的Rails应用程序的第二个请求中,一切都因单元化常量错误而变得糟糕。在生产模式下,我可以成功发出第二个请求,这意味着常量仍然存在。我已通过将以上内容更改为以下内容来解决问题:config.after_initializedorequire'some_cl

  9. ruby-on-rails - 设计注册确认 - 2

    我在我的项目中有一个用户和一个管理员角色。我使用Devise创建了身份验证。在我的管理员角色中,我没有任何确认。在我的用户模型中,我有以下内容:devise:database_authenticatable,:confirmable,:recoverable,:rememberable,:trackable,:validatable,:timeoutable,:registerable#Setupaccessible(orprotected)attributesforyourmodelattr_accessible:email,:username,:prename,:surname,:

  10. Ruby:标准递归模式 - 2

    我经常迷上ruby​​的一件事是递归模式。例如,假设我有一个数组,它可能包含无限深度的数组作为元素。所以,例如:my_array=[1,[2,3,[4,5,[6,7]]]]我想创建一个方法,可以将数组展平为[1,2,3,4,5,6,7]。我知道.flatten可以完成这项工作,但这个问题是作为我经常遇到的递归问题的一个例子-因此我试图找到一个更可重用的解决方案。简而言之-我猜这种事情有一个标准模式,但我想不出任何特别优雅的东西。任何想法表示赞赏 最佳答案 递归是一种方法,它不依赖于语言。您在编写算法时要考虑两种情况:再次调用函数的情

随机推荐