前言

来菜鸟这个大家庭10个月了,总得来说比较融入了环境,同时在忙碌的工作中也深感技术积累不够,在优秀的人身边工作必须更加花时间去提升自己的技术能力、技术视野,所以开一个系列文章,标题就轻松一点叫做最近学习了XXX吧,记录一下自己的学习心得。

由于最近想对系统进行一个小改造,想到使用责任链模式会非常适合,因此就系统地学习总结了一下责任链模式,分享给大家。

责任链模式的定义与特点

责任链模式的定义:使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止

标准的责任链模式,个人总结下来有如下几个特点:

  • 链上的每个对象都有机会处理请求
  • 链上的每个对象都持有下一个要处理对象的引用
  • 链上的某个对象无法处理当前请求,那么它会把相同的请求传给下一个对象

用一张图表示以下使用了责任链模式之后的架构:

也就是说,责任链模式满足了请求发送者与请求处理者之间的松耦合,抽象非核心的部分,以链式调用的方式对请求对象进行处理

这么说不明白?那么下面通过实际例子让你明白。

不使用责任链模式

为什么要使用责任链模式,那么我们得知道不使用责任链模式有什么坏处,然后通过使用责任链模式如何将代码优化。

现在有一个场景:小明要去上学,妈妈给小明列了一些上学前需要做的清单(洗头、吃早饭、洗脸),小明必须按照妈妈的要求,把清单上打钩的事情做完了才可以上学。

首先我们定义一个准备列表PreparationList:

 public class PreparationList {

     /**
* 是否洗脸
*/
private boolean washFace; /**
* 是否洗头
*/
private boolean washHair; /**
* 是否吃早餐
*/
private boolean haveBreakfast; public boolean isWashFace() {
return washFace;
} public void setWashFace(boolean washFace) {
this.washFace = washFace;
} public boolean isWashHair() {
return washHair;
} public void setWashHair(boolean washHair) {
this.washHair = washHair;
} public boolean isHaveBreakfast() {
return haveBreakfast;
} public void setHaveBreakfast(boolean haveBreakfast) {
this.haveBreakfast = haveBreakfast;
} @Override
public String toString() {
return "ThingList [washFace=" + washFace + ", washHair=" + washHair + ", haveBreakfast=" + haveBreakfast + "]";
} }

定义了三件事情:洗头、洗脸、吃早餐。

接着定义一个学习类,按妈妈要求,把妈妈要求的事情做完了再去上学:

 public class Study {

     public void study(PreparationList preparationList) {
if (preparationList.isWashHair()) {
System.out.println("洗脸");
}
if (preparationList.isWashHair()) {
System.out.println("洗头");
}
if (preparationList.isHaveBreakfast()) {
System.out.println("吃早餐");
} System.out.println("我可以去上学了!");
} }

这个例子实现了我们的需求,但是不够优雅,我们的主流程是学习,但是把要准备做的事情这些动作耦合在学习中,这样有两个问题:

  • PreparationList中增加一件事情的时候,比如增加化妆、打扫房间,必须修改study方法进行适配
  • 当这些事情的顺序需要发生变化的时候,必须修改study方法,比如先洗头再洗脸,那么7~9行的代码必须和4~6行的代码互换位置

最糟糕的写法,只是为了满足功能罢了,违背开闭原则,即当我们扩展功能的时候需要去修改主流程,无法做到对修改关闭、对扩展开放。

使用责任链模式

接着看一下使用责任链模式的写法,既然责任链模式的特点是“链上的每个对象都持有下一个对象的引用”,那么我们就这么做。

先抽象出一个AbstractPrepareFilter:

 public abstract class AbstractPrepareFilter {

     private AbstractPrepareFilter nextPrepareFilter;

     public AbstractPrepareFilter(AbstractPrepareFilter nextPrepareFilter) {
this.nextPrepareFilter = nextPrepareFilter;
} public void doFilter(PreparationList preparationList, Study study) {
prepare(preparationList); if (nextPrepareFilter == null) {
study.study();
} else {
nextPrepareFilter.doFilter(preparationList, study);
}
} public abstract void prepare(PreparationList preparationList); }

留一个抽象方法prepare给子类去实现,在抽象类中持有下一个对象的引用nextPrepareFilter,如果有,则执行;如果没有表示链上所有对象都执行完毕,执行Study类的study()方法:

 public class Study {

     public void study() {
System.out.println("学习");
} }

接着我们实现AbstractPrepareList,就比较简单了,首先是洗头:

 public class WashFaceFilter extends AbstractPrepareFilter {

     public WashFaceFilter(AbstractPrepareFilter nextPrepareFilter) {
super(nextPrepareFilter);
} @Override
public void prepare(PreparationList preparationList) {
if (preparationList.isWashFace()) {
System.out.println("洗脸");
} } }

接着洗脸:

 public class WashHairFilter extends AbstractPrepareFilter {

     public WashHairFilter(AbstractPrepareFilter nextPrepareFilter) {
super(nextPrepareFilter);
} @Override
public void prepare(PreparationList preparationList) {
if (preparationList.isWashHair()) {
System.out.println("洗头");
} } }

最后吃早餐:

 public class HaveBreakfastFilter extends AbstractPrepareFilter {

     public HaveBreakfastFilter(AbstractPrepareFilter nextPrepareFilter) {
super(nextPrepareFilter);
} @Override
public void prepare(PreparationList preparationList) {
if (preparationList.isHaveBreakfast()) {
System.out.println("吃早餐");
} } }

最后我们看一下调用方如何编写:

 @Test
public void testResponsibility() {
PreparationList preparationList = new PreparationList();
preparationList.setWashFace(true);
preparationList.setWashHair(false);
preparationList.setHaveBreakfast(true); Study study = new Study(); AbstractPrepareFilter haveBreakfastFilter = new HaveBreakfastFilter(null);
AbstractPrepareFilter washFaceFilter = new WashFaceFilter(haveBreakfastFilter);
AbstractPrepareFilter washHairFilter = new WashHairFilter(washFaceFilter); washHairFilter.doFilter(preparationList, study);
}

至此使用责任链模式修改这段逻辑完成,看到我们完成了学习与准备工作之间的解耦,即核心的事情我们是要学习,此时无论加多少准备工作,都不需要修改study方法,只需要修改调用方即可。

但是这种写法好吗?个人认为这种写法虽然符合开闭原则,但是两个明显的缺点对客户端并不友好:

  • 增加、减少责任链对象,需要修改客户端代码,即比如我这边想要增加一个打扫屋子的操作,那么testResponsibility()方法需要改动
  • AbstractPrepareFilter washFaceFilter = new WashFaceFilter(haveBreakfastFilter)这种调用方式不够优雅,客户端需要思考一下,到底真正调用的时候调用三个Filter中的哪个Filter

为此,我们来个终极版的、升级版的责任链模式。

升级版责任链模式

上面我们写了一个责任链模式,这种是一种初级的符合责任链模式的写法,最后也写了,这种写法是有明显的缺点的,那么接着我们看一下升级版的责任链模式如何写,解决上述问题。

以下的写法也是Servlet的实现方式,首先还是抽象一个Filter:

 public interface StudyPrepareFilter {

     public void doFilter(PreparationList preparationList, FilterChain filterChain);

 }

注意这里多了一个FilterChain,也就是责任链,是用于串起所有的责任对象的,它也是StudyPrepareFilter的一个子类:

 public class FilterChain implements StudyPrepareFilter {

     private int pos = 0;

     private Study study;

     private List<StudyPrepareFilter> studyPrepareFilterList;

     public FilterChain(Study study) {
this.study = study;
} public void addFilter(StudyPrepareFilter studyPrepareFilter) {
if (studyPrepareFilterList == null) {
studyPrepareFilterList = new ArrayList<StudyPrepareFilter>();
} studyPrepareFilterList.add(studyPrepareFilter);
} @Override
public void doFilter(PreparationList thingList, FilterChain filterChain) {
// 所有过滤器执行完毕
if (pos == studyPrepareFilterList.size()) {
study.study();
} studyPrepareFilterList.get(pos++).doFilter(thingList, filterChain);
} }

即这里有一个计数器,假设所有的StudyPrepareFilter没有调用完毕,那么调用下一个,否则执行Study的study()方法。

接着就比较简单了,实现StudyPrepareFilter类即可,首先还是洗头:

 public class WashHairFilter implements StudyPrepareFilter {

     @Override
public void doFilter(PreparationList preparationList, FilterChain filterChain) {
if (preparationList.isWashHair()) {
System.out.println("洗完头发");
} filterChain.doFilter(preparationList, filterChain);
} }

注意,这里每个实现类需要显式地调用filterChain的doFilter方法。洗脸:

 public class WashFaceFilter implements StudyPrepareFilter {

     @Override
public void doFilter(PreparationList preparationList, FilterChain filterChain) {
if (preparationList.isWashFace()) {
System.out.println("洗完脸");
} filterChain.doFilter(preparationList, filterChain);
} }

吃早饭:

 public class HaveBreakfastFilter implements StudyPrepareFilter {

     @Override
public void doFilter(PreparationList preparationList, FilterChain filterChain) {
if (preparationList.isHaveBreakfast()) {
System.out.println("吃完早饭");
} filterChain.doFilter(preparationList, filterChain);
} }

最后看一下调用方:

 @Test
public void testResponsibilityAdvance() {
PreparationList preparationList = new PreparationList();
preparationList.setWashFace(true);
preparationList.setWashHair(false);
preparationList.setHaveBreakfast(true); Study study = new Study(); StudyPrepareFilter washFaceFilter = new WashFaceFilter();
StudyPrepareFilter washHairFilter = new WashHairFilter();
StudyPrepareFilter haveBreakfastFilter = new HaveBreakfastFilter(); FilterChain filterChain = new FilterChain(study);
filterChain.addFilter(washFaceFilter);
filterChain.addFilter(washHairFilter);
filterChain.addFilter(haveBreakfastFilter); filterChain.doFilter(preparationList, filterChain);
}

完美解决第一版责任链模式存在的问题,至此增加、修改责任对象客户端调用代码都不需要再改动。

有的人可能会问,你这个增加、减少责任对象,testResponsibilityAdvance()方法,不是还得addFilter,或者删除一行吗?我们回想一下,Servlet我们增加或减少Filter需要改动什么代码吗?不用,我们需要改动的只是web.xml而已。同样的道理,FilterChain里面有studyPrepareFilterList,我们完全可以把FilterChain做成一个Spring Bean,所有的Filter具体实现类也都是Spring Bean,注入studyPrepareFilterList就好了,伪代码为:

 <bean id="filterChain" class="xxx.xxx.xxx.FilterChain">
<property name="studyPrepareFilterList">
<list>
<ref bean="washFaceFilter" />
<ref bean="washHairFilter" />
<ref bean="haveBreakfastFilter" />
</list>
</property>
</bean>

这样是不是完美解决了问题?我们新增、减少Filter,或者修改Filter顺序,只需要修改.xml文件即可,不仅核心逻辑符合开闭原则,调用方也符合开闭原则。

责任链模式的使用场景

这个就不多说了,最典型的就是Servlet中的Filter,有了上面的分析,大家应该也可以理解Servlet中责任链模式的工作原理了,然后为什么一个一个的Filter需要配置在web.xml中。

责任链模式的结构

想想看,好像责任链模式也没有什么太复杂的结构,将责任抽象,实现责任接口,客户端发起调用,网上找了一张图表示一下:

责任链模式的优点及使用场景

最后说说责任链模式的优点吧,大致有以下几点:

  • 实现了请求发送者与请求处理者之间的松耦合
  • 可动态添加责任对象、删除责任对象、改变责任对象顺序,非常灵活
  • 每个责任对象专注于做自己的事情,职责明确

什么时候需要用责任链模式?这个问题我是这么想的:系统设计的时候,注意区分主次就好,即哪部分是核心流程,哪部分是辅助流程,辅助流程是否有N多if...if...if...的场景,如果是且每个if都有一个统一的抽象,那么抽象辅助流程,把每个if作为一个责任对象进行链式调用,优雅实现,易复用可扩展。

Java设计模式13:责任链模式的更多相关文章

  1. 详解java设计模式之责任链模式

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt175 从击鼓传花谈起 击鼓传花是一种热闹而又紧张的饮酒游戏.在酒宴上宾客依次 ...

  2. JAVA设计模式之责任链模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述责任链(Chain of Responsibility)模式的: 责任链模式是一种对象的行为模式.在责任链模式里,很多对象由每一个对象对其 ...

  3. Java设计模式之责任链模式、职责链模式

    本文继续介绍23种设计模式系列之职责链模式.   什么是链 1.链是一系列节点的集合. 2..链的各节点可灵活拆分再重组.   职责链模式 使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间 ...

  4. Java设计模式之八 ----- 责任链模式和命令模式

    前言 在上一篇中我们学习了结构型模式的享元模式和代理模式.本篇则来学习下行为型模式的两个模式, 责任链模式(Chain of Responsibility Pattern)和命令模式(Command ...

  5. Java设计模式应用——责任链模式

    生产一个产品,需要依次执行多个步骤,才能完成,那么是使用责任链模式则是极好的. 在性能告警模块开发过程中,创建一条告警规则需要执行阈值解析,中间表生成,流任务生成,规则入库,告警事件入库等诸多操作.如 ...

  6. java设计模式之责任链模式(Chain of Responsibility)

    转自:http://www.cnblogs.com/java-my-life/archive/2012/05/28/2516865.html 在阎宏博士的<JAVA与模式>一书中开头是这样 ...

  7. java设计模式之责任链模式以及在java中作用

    责任链模式是一种对象的行为模式.在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链.请求在这个链上传递,直到链上的某一个对象决定处理此请求.发出这个请求的客户端并不知道链上的哪一个 ...

  8. 击鼓传花联想到了Java设计模式:责任链模式

    目录 应用场景 简单示例 责任链模式 定义 意图 主要解决问题 何时使用 优缺点 击鼓传花的故事 应用场景 http web请求处理,请求过来后将经过转码.解析.参数封装.鉴权等一系列的处理(责任), ...

  9. Java设计模式---ChainOfResponsibility责任链模式

    参考于 : 大话设计模式 马士兵设计模式视频 代码参考于马士兵设计模式视频 写在开头:职责链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系 图来自大话设计模式,下面我的代 ...

  10. [设计模式] 13 责任链模式 Chain of Responsibility

    转    http://blog.csdn.net/wuzhekai1985   http://www.jellythink.com/archives/878 向项目经理提交了休假申请,我的项目经理向 ...

随机推荐

  1. HTML标签-->HTML5新增

    HTML的Form新增属性 <form method="post" name="myForm"> <p> 邮箱:<input ty ...

  2. 爬虫框架Scrapy入门——爬取acg12某页面

    1.安装1.1自行安装python3环境1.2ide使用pycharm1.3安装scrapy框架2.入门案例2.1新建项目工程2.2配置settings文件2.3新建爬虫app新建app将start_ ...

  3. java 算法之 两个字符串中最大相同的子串

    public class String_intern { public static void main(String[] args) { String old="aaaaabc1" ...

  4. 关于小码哥kylin

    技术格言: 用双手改变人生,用代码改变世界! 个人网站:http://www.isainttech.com QQ:56619556 Email:dragonsaint@qq.com 微信:kylin1 ...

  5. PHP命令空间namespace及use的用法

    使用namespace的目的 命名空间将代码划分出不同的空间(区域),每个空间的常量.函数.类(为了偷懒,我下边都将它们称为元素)的名字互不影响, 这个有点类似我们常常提到的'封装'的概念. 团队合作 ...

  6. Flink入门(二)——Flink架构介绍

    1.基本组件栈 了解Spark的朋友会发现Flink的架构和Spark是非常类似的,在整个软件架构体系中,同样遵循着分层的架构设计理念,在降低系统耦合度的同时,也为上层用户构建Flink应用提供了丰富 ...

  7. 大型情感剧集Selenium:2_options设置 #华为云·寻找黑马程序员#

    上集回顾 昨天说简单介绍了什么是selenium,它能干what,和发展史与梗概.当的是python如何通过pip安装selenium,并下载对应浏览器的webdriver. 最后简单通过一个Demo ...

  8. 【nodejs原理&源码赏析(3)】欣赏手术级的原型链加工艺术

    [摘要] 学习经典代码中的prototype加工 示例代码托管在:http://www.github.com/dashnowords/blogs 好的代码都差不多,烂的代码却各有各的烂法. 一. 概述 ...

  9. JVM前奏篇(大局观)

    话不多说直接上干货,先来看oracle官网中是怎么描述JDK的:https://docs.oracle.com/javase/8/docs/index.html 这是官网中JDK.JRE.JVM的一个 ...

  10. luogu P2824 [HEOI2016/TJOI2016]排序

    题目描述 在2016年,佳媛姐姐喜欢上了数字序列.因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题,需要你来帮助他.这个难题是这样子的:给出一个1到n的全排列,现在对这个全排列序列进行 ...