专栏首页Java后端技术栈cwnait设计模式 | 必备的责任链设计模式

设计模式 | 必备的责任链设计模式

概念

职责链模式(Chain of Responsibility)

使多个对象都有机会处理同一个请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

责任链设计模式中的角色

Handler

抽象处理者(定义一个处理请求的接口,接口可以定义出一个方法,以设定和返回对下家的引用。通常由一个抽象类或接口实现

Concrete Handler

具体处理者(具体请求者在接受到请求后,如果满足条件则自己处理请求,否则将请求传到下家。具体处理者持有下家对象的引用)

Client

客户端(调用者)

责任链设计模式类图

链表VS责任链

责任链模式为某个请求创建一个对象链,每个对象依次检查此请求,并对其进行处理,或者将它传给链中的下一个对象。

链表是很常见一种数据结构,链表中的每一个节点都是一个对象,并且该对象中存储着下一个节点的指针。链表的基本结构如下:

责任链模式的结构其实和链表很类似,存在的区别就是责任链模式中所有的对象都有一个共同的父类( 或接口 ):

在责任链模式中,N 个 Handler 子类都处理同一个请求,只不过具体的职责有所差别。

当有一个请求进入时,先经过 AHandler 的 handlerRequest 方法,然后再把请求传递给 BHandler ,B 处理完再把请求传递给 CHandler ,以此类推,形成一个链条。链条上的每一个对象所承担的责任各不相同,这就是责任链模式。

实战案例

现在我们模拟一个场景:论坛用户发表帖子,但是常常会有用户发一些不良的信息,如广告信息、涉黄信息、涉及政治的敏感词等。这时我们就可以使用责任链模式来过滤用户发表的信息。

先定义所有责任链对象的父类:

/**
 * 帖子处理器
 */
public abstract class PostHandler {

    /**
     * 后继者
     */
    protected PostHandler successor;

    public void setSuccessor(PostHandler handler){
        this.successor = handler;
    }

    public abstract void handlerRequest(Post post);

    protected final void next(Post post){
        if(this.successor != null){
            this.successor.handlerRequest(post);
        }
    }
}

父类 Handler 主要封装了传递请求等方法,其中要注意的有以下几点:

  • successor ( 后继者 ),这个属性很重要,它保存了责任链中下一个处理器。
  • 在 next() 方法中( 方法名自己随便取 ),当请求传递到最后一个责任对象时,已经没有后继者继续处理请求了,因此要对 successor 做判空处理,避免抛出空指针异常。
  • 处理请求的 handlerRequest 的入参和返回类型可以根据实际情况修改,可以在该方法中抛出异常来中断请求。

广告处理器:

/**
 * 广告处理器
 */
public class AdHandler extends PostHandler {

    @Override
    public void handlerRequest(Post post) {
        //屏蔽广告内容
        String content = post.getContent();
        //.....
        content = content.replace("广告","**");
        post.setContent(content);

        System.out.println("过滤广告...");
        //传递给下一个处理器
        next(post);
    }
}

涉黄处理器:

/**
 * 涉黄处理器
 */
public class YellowHandler extends PostHandler {

    @Override
    public void handlerRequest(Post post) {
        //屏蔽涉黄内容
        String content = post.getContent();
        //.....
        content = content.replace("涉黄","**");
        post.setContent(content);

        System.out.println("过滤涉黄内容...");
        //传递给下一个处理器
        next(post);
    }
}

敏感词处理器:

/**
 * 敏感词处理器
 */
public class SensitiveWordsHandler extends PostHandler {

    @Override
    public void handlerRequest(Post post) {
        //屏蔽敏感词
        String content = post.getContent();
        //.....
        content = content.replace("敏感词","**");
        post.setContent(content);

        System.out.println("过滤敏感词...");
        //传递给下一个处理器
        next(post);
    }
}

三个责任链对象的结构基本一致,只有具体的业务处理逻辑不同。上面代码中将所有不健康内容都用 “*” 号代替。

调用:

//创建责任对象
PostHandler adHandler = new AdHandler();
PostHandler yellowHandler = new YellowHandler();
PostHandler swHandler = new SensitiveWordsHandler();

//形成责任链
yellowHandler.setSuccessor(swHandler);
adHandler.setSuccessor(yellowHandler);

Post post = new Post();
post.setContent("我是正常内容,我是广告,我是涉黄,我是敏感词,我是正常内容");

System.out.println("过滤前的内容为:"+post.getContent());

post = adHandler.handlerRequest(post);

System.out.println("过滤后的内容为:"+post.getContent());

调用结果:

看到这里,相信你已经基本掌握了责任链模式。但问题来了,我直接将过滤不良信息写在一个方法里不行吗?比如:

public class PostUtil {

    public void filterContent(Post post){

        String content = post.getContent();

        content = content.replace("广告","**");
        content = content.replace("涉黄","**");
        content = content.replace("敏感词","**");

        post.setContent(content);
    }
}

相比之下,这种方式更简单,仅仅几行代码就搞定了。为什么还要用责任链模式呢?

大家还记得开闭原则吗?如果后面要增加其他的功能,过滤其他类型的内容,我们还得修改上面的 filterContent 方法,违背了开闭原则。如果你是一个框架开发者,你希望别人修改你框架的源码吗?

因此我们需要使用责任链模式,能够在不修改已有代码的情况下扩展新功能。

经典案例

1. Servlet 中的 Filter

Servlet 中的过滤器 Filter 就是典型的责任链模式,假如我们要给每一次 Http 请求都打印一个 log ,就可以使用 Filter 过滤器来实现:

创建一个 Filter 实现 Filter 接口:

public class LogFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        System.out.println("write log");
        filterChain.doFilter(servletRequest,servletResponse);

    }

    @Override
    public void destroy() {

    }
}

然后将这个过滤器配置到 web.xml 中:

<filter>
  <filter-name>LogFilter</filter-name>
  <filter-class>com.zhoujun.filter.LogFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name>LogFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

在上面 LogFilter 类中,我们可以看到 servlet 的责任链是通过 Filter 来实现的,这是一个接口,在 doFilter 中还用到了 FilterChain ,也是一个接口。通过查找源码,发现了 FilterChain 的其中一个实现类:

public class PassThroughFilterChain implements FilterChain {
    @Nullable
    private Filter filter;
    @Nullable
    private FilterChain nextFilterChain;
    @Nullable
    private Servlet servlet;

    public PassThroughFilterChain(Filter filter, FilterChain nextFilterChain) {
        Assert.notNull(filter, "Filter must not be null");
        Assert.notNull(nextFilterChain, "'FilterChain must not be null");
        this.filter = filter;
        this.nextFilterChain = nextFilterChain;
    }

    public PassThroughFilterChain(Servlet servlet) {
        Assert.notNull(servlet, "Servlet must not be null");
        this.servlet = servlet;
    }

    public void doFilter(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        if (this.filter != null) {
            this.filter.doFilter(request, response, this.nextFilterChain);
        } else {
            Assert.state(this.servlet != null, "Neither a Filter not a Servlet set");
            this.servlet.service(request, response);
        }

    }
}

请仔细看这个实现类,你会发现其结构和我们之前的 PostHandler 示例代码极其相似,该类中的 Private FilterChain nextFilterChain; 相当于 PostHandler 中的后继者 Successor 。

将我们自定义的 Filter 配置到 web.xml 中的操作就是将该对象添加到责任链上,Servlet 开发者帮我们完成了 setSuccessor() 的操作。 责任链设计模式在Spring、Dubbo、Mybatis等框架中也有大量的应用。

总结

责任链模式经常用于过滤器,拦截器,事件( 鼠标键盘事件,冒泡事件等 )等场景。

优点

  • 请求者和接收者解耦;
  • 可以动态地增加或减少责任链上的对象,或者修改顺序。

缺点

  • 调用者不知道请求可能被哪些责任链对象处理,不利于排错;
  • 用户请求可能被责任链中途拦截,最终未必被真正执行,这点既是优点也是缺点,我们可以利用它做权限控制拦截器。

GOF23种设计模式类型、描述和类图(上)

GOF23种设计模式类型、描述和类图(中)

GOF23种设计模式类型、描述和类图(下)

【文章汇总】设计模式篇

Java中的门面设计模式及如何用代码实现

本文分享自微信公众号 - Java后端技术栈(t-j20120622),作者:田老师

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-05-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 分布式锁的三种实现

    在单体时代使用ReentrantLock、synchronized等来实锁,以便保证资源的安全性。但是在分布式时代,则需要分布式锁才能实现资源的安全性。

    田维常
  • 如何搞定Mybatis 中的 9 种设计模式

    虽然我们都知道有很多设计模式,但是大多停留在概念层面,真实开发中很少遇到,Mybatis源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,能够更深...

    田维常
  • RabbitMQ和Kafka到底怎么选?

    开源社区有好多优秀的队列中间件,比如RabbitMQ和Kafka,每个队列都貌似有其特性,在进行工程选择时,往往眼花缭乱,不知所措。对于RabbitMQ和Kaf...

    田维常
  • c# winform 窗体失去焦点关闭(钩子实现)

    main函数里面写 Application.ApplicationExit += Application_ApplicationExit;

    冰封一夏
  • 图像识别基本算法之SURF

    俺踏月色而来
  • 利用Headless实现无UI自动化

    iTesting,爱测试,爱分享 在做自动化的时, 偶尔你需要打开一个页面获取某些字段,但由于某些原因(通常是安全), 你又不想这个页面展示出来, 这个时候,不...

    iTesting
  • 性能测试常见瓶颈分析及调优方法

    在性能测试过程中,最重要的一部分就是性能瓶颈定位与调优。而引发性能瓶颈的原因是多种多样的,在之前的博客:常见的性能测试缺陷有进行介绍。这篇文章,来聊聊性能测试过...

    写博客的老张
  • loadrunner 运行脚本-Run-time Settings-ContentCheck简单设置

    ContentCheck的设置可用来让VuGen检测存在错误的站点页面。如果被测的Web应用没有使用自定义的错误页面,那么这里不用添加规则,因为LR在回放时候,...

    授客
  • 一周极客热文:微软将推新编程语言M#:系统编程级别的C#

    据国外媒体VB报道,微软正在开发一款新的编程语言:M# 。它在C#的基础上添加了系统编程特性,可用来构建各种类型的应用,尤其是云计算应用。 微软员工Joe Du...

    钱曙光
  • 小程序优化36计

    本文偏技术,可能较枯燥,阅读完大概需要 15分钟 微信小程序转眼上线将近一年了,提供了接近原生App的使用体验,加上一年来不断释放新的能力,获得的关注越来越多。...

    企鹅号小编

扫码关注云+社区

领取腾讯云代金券