前言

来菜鸟这个大家庭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. 【数据结构】之栈(Java语言描述)

    在前面的[这篇文章]中,我简单介绍了栈这种数据结构的操作功能,并使用C语言对其进行了代码的编写. Java的JDK中默认为我们提供了栈这种数据结构的API—— Stack . Java中的Stack类 ...

  2. 【BZOJ2190】【Luogu P2158】 [SDOI2008]仪仗队

    前言: 更不好的阅读 这篇题解真的写了很久,改了又改才成为这样的,我不会写题解但我正在努力去学,求通过,求赞... 题目: BZOJ Luogu 思路: 像我这样的数论菜鸡就不能一秒切这题,怎么办呢? ...

  3. Block循环引用问题

    根控制器没办法销毁,除非程序退出 从一个控制器跳到另外一个控制器,调用该控制器的pop方法才会销毁该控制器 self是一个强指针 在block中使用self时要注意循环引用的问题 最好将当前block ...

  4. hibernate绑定session

    session session是一种单实例对象 简单说就是自己用 别人不能用.在一些项目中很多人一起来操作 所以我们可以把session与我们的本地线程一起绑定,本地线程的特点就是执行一次 从创建到销 ...

  5. CodeForces985G Team Players

    G. Team Players time limit per test 2 seconds memory limit per test 256 megabytes input standard inp ...

  6. 2019企业linux运维最需要的了解的一些硬件基础知识

    第3章 服务器    245 3.1 电脑的种类    245 3.2 服务器的介绍    246 3.2.1 服务器的类别    246 3.2.2 服务器的性能    247 3.2.3 服务器的 ...

  7. 【同步工具类】CountDownLatch闭锁任务同步

    [同步工具类]CountDownLatch闭锁任务同步 转载:https://www.cnblogs.com/yangchongxing/p/9214284.html 打过dota的同学都知道,多人一 ...

  8. webpack学习_管理输出(管理资源插件)

    管理输出步骤 Step1:在src新建文件print.js添加逻辑 Step2:在src/index.js import 引用新添加的逻辑 Step3:更新dist/index.html文件,修改引入 ...

  9. CodeForces - 1073D Berland Fair

    XXI Berland Annual Fair is coming really soon! Traditionally fair consists of nnbooths, arranged in ...

  10. hdu 1394 Minimum Inversion Number (树状数组求逆序对)

    The inversion number of a given number sequence a1, a2, ..., an is the number of pairs (ai, aj) that ...