zoukankan      html  css  js  c++  java
  • Java设计模式13:责任链模式

    前言

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

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

    责任链模式的定义与特点

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

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

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

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

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

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

    不使用责任链模式

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

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

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

     1 public class PreparationList {
     2 
     3     /**
     4      * 是否洗脸
     5      */
     6     private boolean washFace;
     7     
     8     /**
     9      * 是否洗头
    10      */
    11     private boolean washHair;
    12     
    13     /**
    14      * 是否吃早餐
    15      */
    16     private boolean haveBreakfast;
    17 
    18     public boolean isWashFace() {
    19         return washFace;
    20     }
    21 
    22     public void setWashFace(boolean washFace) {
    23         this.washFace = washFace;
    24     }
    25 
    26     public boolean isWashHair() {
    27         return washHair;
    28     }
    29 
    30     public void setWashHair(boolean washHair) {
    31         this.washHair = washHair;
    32     }
    33 
    34     public boolean isHaveBreakfast() {
    35         return haveBreakfast;
    36     }
    37 
    38     public void setHaveBreakfast(boolean haveBreakfast) {
    39         this.haveBreakfast = haveBreakfast;
    40     }
    41 
    42     @Override
    43     public String toString() {
    44         return "ThingList [washFace=" + washFace + ", washHair=" + washHair + ", haveBreakfast=" + haveBreakfast + "]";
    45     }
    46     
    47 }

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

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

     1 public class Study {
     2 
     3     public void study(PreparationList preparationList) {
     4         if (preparationList.isWashHair()) {
     5             System.out.println("洗脸");
     6         }
     7         if (preparationList.isWashHair()) {
     8             System.out.println("洗头");
     9         }
    10         if (preparationList.isHaveBreakfast()) {
    11             System.out.println("吃早餐");
    12         }
    13         
    14         System.out.println("我可以去上学了!");
    15     }
    16     
    17 }

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

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

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

    使用责任链模式

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

    先抽象出一个AbstractPrepareFilter:

     1 public abstract class AbstractPrepareFilter {
     2 
     3     private AbstractPrepareFilter nextPrepareFilter;
     4     
     5     public AbstractPrepareFilter(AbstractPrepareFilter nextPrepareFilter) {
     6         this.nextPrepareFilter = nextPrepareFilter;
     7     }
     8 
     9     public void doFilter(PreparationList preparationList, Study study) {
    10         prepare(preparationList);
    11         
    12         if (nextPrepareFilter == null) {
    13             study.study();
    14         } else {
    15             nextPrepareFilter.doFilter(preparationList, study);
    16         }
    17     }
    18     
    19     public abstract void prepare(PreparationList preparationList);
    20     
    21 }

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

     1 public class Study {
     2 
     3     public void study() {
     4         System.out.println("学习");
     5     }
     6     
     7 }

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

     1 public class WashFaceFilter extends AbstractPrepareFilter {
     2     
     3     public WashFaceFilter(AbstractPrepareFilter nextPrepareFilter) {
     4         super(nextPrepareFilter);
     5     }
     6 
     7     @Override
     8     public void prepare(PreparationList preparationList) {
     9         if (preparationList.isWashFace()) {
    10             System.out.println("洗脸");
    11         }
    12         
    13     }
    14     
    15 }

    接着洗脸:

     1 public class WashHairFilter extends AbstractPrepareFilter {
     2     
     3     public WashHairFilter(AbstractPrepareFilter nextPrepareFilter) {
     4         super(nextPrepareFilter);
     5     }
     6 
     7     @Override
     8     public void prepare(PreparationList preparationList) {
     9         if (preparationList.isWashHair()) {
    10             System.out.println("洗头");
    11         }
    12         
    13     }
    14     
    15 }

    最后吃早餐:

     1 public class HaveBreakfastFilter extends AbstractPrepareFilter {
     2     
     3     public HaveBreakfastFilter(AbstractPrepareFilter nextPrepareFilter) {
     4         super(nextPrepareFilter);
     5     }
     6 
     7     @Override
     8     public void prepare(PreparationList preparationList) {
     9         if (preparationList.isHaveBreakfast()) {
    10             System.out.println("吃早餐");
    11         }
    12         
    13     }
    14     
    15 }

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

     1 @Test
     2 public void testResponsibility() {
     3     PreparationList preparationList = new PreparationList();
     4     preparationList.setWashFace(true);
     5     preparationList.setWashHair(false);
     6     preparationList.setHaveBreakfast(true);
     7         
     8     Study study = new Study();
     9         
    10     AbstractPrepareFilter haveBreakfastFilter = new HaveBreakfastFilter(null);
    11     AbstractPrepareFilter washFaceFilter = new WashFaceFilter(haveBreakfastFilter);
    12     AbstractPrepareFilter washHairFilter = new WashHairFilter(washFaceFilter);
    13         
    14     washHairFilter.doFilter(preparationList, study);
    15 }

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

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

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

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

    升级版责任链模式

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

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

     1 public interface StudyPrepareFilter {
     2 
     3     public void doFilter(PreparationList preparationList, FilterChain filterChain);
     4     
     5 }

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

     1 public class FilterChain implements StudyPrepareFilter {
     2 
     3     private int pos = 0;
     4 
     5     private Study study;
     6     
     7     private List<StudyPrepareFilter> studyPrepareFilterList;
     8     
     9     public FilterChain(Study study) {
    10         this.study = study;
    11     }
    12 
    13     public void addFilter(StudyPrepareFilter studyPrepareFilter) {
    14         if (studyPrepareFilterList == null) {
    15             studyPrepareFilterList = new ArrayList<StudyPrepareFilter>();
    16         }
    17         
    18         studyPrepareFilterList.add(studyPrepareFilter);
    19     }
    20     
    21     @Override
    22     public void doFilter(PreparationList thingList, FilterChain filterChain) {
    23         // 所有过滤器执行完毕
    24         if (pos == studyPrepareFilterList.size()) {
    25             study.study();
    26         }
    27         
    28         studyPrepareFilterList.get(pos++).doFilter(thingList, filterChain);
    29     }
    30     
    31 }

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

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

     1 public class WashHairFilter implements StudyPrepareFilter {
     2 
     3     @Override
     4     public void doFilter(PreparationList preparationList, FilterChain filterChain) {
     5         if (preparationList.isWashHair()) {
     6             System.out.println("洗完头发");
     7         }
     8         
     9         filterChain.doFilter(preparationList, filterChain);
    10     }
    11     
    12 }

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

     1 public class WashFaceFilter implements StudyPrepareFilter {
     2 
     3     @Override
     4     public void doFilter(PreparationList preparationList, FilterChain filterChain) {
     5         if (preparationList.isWashFace()) {
     6             System.out.println("洗完脸");
     7         }
     8         
     9         filterChain.doFilter(preparationList, filterChain);
    10     }
    11     
    12 }

    吃早饭:

     1 public class HaveBreakfastFilter implements StudyPrepareFilter {
     2 
     3     @Override
     4     public void doFilter(PreparationList preparationList, FilterChain filterChain) {
     5         if (preparationList.isHaveBreakfast()) {
     6             System.out.println("吃完早饭");
     7         }
     8         
     9         filterChain.doFilter(preparationList, filterChain);
    10     }
    11     
    12 }

    最后看一下调用方:

     1 @Test
     2 public void testResponsibilityAdvance() {
     3     PreparationList preparationList = new PreparationList();
     4     preparationList.setWashFace(true);
     5     preparationList.setWashHair(false);
     6     preparationList.setHaveBreakfast(true);
     7         
     8     Study study = new Study();
     9         
    10     StudyPrepareFilter washFaceFilter = new WashFaceFilter();
    11     StudyPrepareFilter washHairFilter = new WashHairFilter();
    12     StudyPrepareFilter haveBreakfastFilter = new HaveBreakfastFilter();
    13         
    14     FilterChain filterChain = new FilterChain(study);
    15     filterChain.addFilter(washFaceFilter);
    16     filterChain.addFilter(washHairFilter);
    17     filterChain.addFilter(haveBreakfastFilter);
    18         
    19     filterChain.doFilter(preparationList, filterChain);
    20 }

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

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

    1 <bean id="filterChain" class="xxx.xxx.xxx.FilterChain">
    2     <property name="studyPrepareFilterList">
    3         <list>
    4             <ref bean="washFaceFilter" />
    5             <ref bean="washHairFilter" />
    6             <ref bean="haveBreakfastFilter" />
    7         </list>
    8     </property>
    9 </bean>

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

    责任链模式的使用场景

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

    责任链模式的结构

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

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

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

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

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

  • 相关阅读:
    JAVA小技能-之远程调试
    征集系统功能开发进度总结
    征集系统功能开发进度总结
    linux常用的监控命令
    Makefile中 =、:=和 += 的区别
    Linux驱动编译错误:implicit declaration of function “copy_form_user”,“copy_to_user“
    Android 第一个驱动之 word_count(一)
    Ubuntu16.04 默认 gcc、g++ 版本过高会导致Android2.3.4 , Android 4.0.1_r1 编译报错
    降低 make 版本教程
    Tensorflow教程分享:TensorFlow 基础详解
  • 原文地址:https://www.cnblogs.com/xrq730/p/10633761.html
Copyright © 2011-2022 走看看