设计模式责任链模式

  1. 责任链模式

    1.简介

    责任链模式是一种行为设计模式,允许你将请求沿着处理者链进行发送。收到请求后,每个处理者均可对请求进行处理,或将其传递给链上的下一个处理者。

    其实就是将请求的发送和接受解耦,让多个接受对象都有机会处理这个请求。将这些接受对象串成一条链,并沿着这条链传递这个请求,直到链上的某个对象能够处理它为止。

    2.UML图

    责任链模式1.png

    • **处理者(Handler):**声明了所有具体处理者的通用接口。该接口通常仅包含单个方法用于请求处理,但有时其还会包含一个设置链上下个处理者的方法。
    • **基础处理者(Base Handler):**是一个可选的类,你可以将所有处理者共用的代码放置在其中。通常情况下,该类中定义了一个保存对于下个处理者引用的成员变量。客户端可通过将处理者传递给上个处理者的构造函数或者设定方法来创建链。该类还可以实现默认的处理行为:确定下个处理者存在后再将请求传递给它。
    • **具体处理者(Concrete Handlers):**包含处理请求的实际代码。每个处理者接收到请求后,都必须决定是否进行处理,以及是否沿着链传递请求。处理者通常是独立且不可变的,需要通过构造函数一次性获取所有的必要数据。
    • **客户端(Client):**可根据程序逻辑一次性或者动态的生成链,需要注意的是,请求可以发送给链上任意一个处理者,也并非一定要从第一个开始。

    3.代码示例

    设想一个场景,小白的妈妈给小白列了一个清单(洗头、洗脸、吃早饭),小白需要吧清单上的任务完成之后才可以开始学习。

    如果我们不使用任何模式:

    首先定义一个准备列表:

    package com.gs.designmodel.chainofduty.bad;

import lombok.Data;

/**

  • @author: Gaos
  • @Date: 2023-08-28 20:15 **/ @Data 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; } }

    学习类:

    package com.gs.designmodel.chainofduty.bad;

/**

  • @author: Gaos
  • @Date: 2023-08-28 20:19 **/ public class Test {

    public static void main(String[] args) { PreparationList preparationList = new PreparationList(); if(preparationList.isWashHair()) { System.out.println("洗头"); } if(preparationList.isWashFace()) { System.out.println("洗脸"); } if(preparationList.isHaveBreakfast()) { System.out.println("吃早饭"); } System.out.println("任务完成"); } }

    这个例子虽然实现了我们的需求,但是也有一些问题:

    • 准备列表类中如果增加一件事情的时候,你必须修改study类中的方法进行适配。
    • 如果这些事情的顺序现在需要修改,那么你也必须要修改study类中相对应的方法。

    下面我们使用一下责任链模式,它的特点是 链上的每个对象都持有下个对象的引用。

    首先我们抽象出一个 AbstractPrepareFilter:

    package com.gs.designmodel.chainofduty.normal;

import com.gs.designmodel.chainofduty.bad.PreparationList;

/**

  • @author: Gaos
  • @Date: 2023-08-28 20:21 **/ public abstract class AbstractPrepareFilter {

    // 持有下一个对象的引用 private AbstractPrepareFilter next;

    public AbstractPrepareFilter(AbstractPrepareFilter next) { this.next = next; } // 如果有下一个对象就执行,没有的话就代表所有对象执行完毕 public void doFilter(PreparationList preparationList, Study study) { prepare(preparationList); if(next == null) { study.study(); }else { next.doFilter(preparationList, study); } }

    // 留一个抽象方法给子类实现 public abstract void prepare(PreparationList preparationList); }

    然后我们开始实现各个功能:

    洗脸:

    package com.gs.designmodel.chainofduty.normal;

import com.gs.designmodel.chainofduty.bad.PreparationList;

/**

  • @author: Gaos
  • @Date: 2023-08-28 20:25 **/ public class WashFaceFilter extends AbstractPrepareFilter{

    public WashFaceFilter(AbstractPrepareFilter next) { super(next); }

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

    洗头:

    package com.gs.designmodel.chainofduty.normal;

import com.gs.designmodel.chainofduty.bad.PreparationList;

/**

  • @author: Gaos
  • @Date: 2023-08-28 20:27 **/ public class WashHairFilter extends AbstractPrepareFilter{

    public WashHairFilter(AbstractPrepareFilter next) { super(next); }

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

    吃早饭:

    package com.gs.designmodel.chainofduty.normal;

import com.gs.designmodel.chainofduty.bad.PreparationList;

/**

  • @author: Gaos
  • @Date: 2023-08-28 20:28 **/ public class EatBreakfastFilter extends AbstractPrepareFilter{

    public EatBreakfastFilter(AbstractPrepareFilter next) { super(next); }

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

    学习类:

    package com.gs.designmodel.chainofduty.normal;

/**

  • @author: Gaos
  • @Date: 2023-08-28 20:23 **/ public class Study {

    public void study() { System.out.println("计划完成,开始学习"); } }

    测试类(调用方):

    package com.gs.designmodel.chainofduty.normal;

import com.gs.designmodel.chainofduty.bad.PreparationList;

/**

  • @author: Gaos
  • @Date: 2023-08-28 20:28 **/ public class Test { public static void main(String[] args) { PreparationList preparationList = new PreparationList(); preparationList.setWashFace(true); preparationList.setWashHair(false); preparationList.setHaveBreakfast(true);

    Study study = new Study();
    AbstractPrepareFilter haveBreakfastFilter = new EatBreakfastFilter(null);
    AbstractPrepareFilter washFaceFilter = new WashFaceFilter(haveBreakfastFilter);
    AbstractPrepareFilter washHairFilter = new WashHairFilter(washFaceFilter);
    
    washHairFilter.doFilter(preparationList, study);

    } }

    结果:

    洗脸
    吃早餐
    计划完成,开始学习
    

    这种方式虽然使用了责任链模式,并将学习和准备工作之间接耦,无论加多少准备工作都不需要修改study中的方法,但是也还有一些问题。

    • 增加或者减少责任链对象,都需要修改客户端的代码。
    • AbstractPrepareFilter washFaceFilter = new WashFaceFilter(haveBreakfastFilter),这种方式不够优雅,很容易搞不清楚到底会调用哪个责任链对象。

    我们针对上述问题再做一个升级版:

    首先还是抽象一个 Filter:

    package com.gs.designmodel.chainofduty.special;

import com.gs.designmodel.chainofduty.bad.PreparationList;

/**

  • @author: Gaos
  • @Date: 2023-08-28 20:30 **/ public interface StudyPrepareFilter {

    public void doFilter(PreparationList preparationList, FilterChain filterChain); }

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

    package com.gs.designmodel.chainofduty.special;

import com.gs.designmodel.chainofduty.bad.PreparationList; import com.gs.designmodel.chainofduty.normal.Study;

import java.util.ArrayList; import java.util.List;

/**

  • @author: Gaos
  • @Date: 2023-08-28 20:32 **/ public class FilterChain implements StudyPrepareFilter{

    // 计数器 private int pos = 0;

    private Study study;

    private List studyPrepareFilters;

    public FilterChain(Study study) { this.study = study; }

    public void addFilter(StudyPrepareFilter studyPrepareFilter) { if(studyPrepareFilters == null) { studyPrepareFilters = new ArrayList(); } studyPrepareFilters.add(studyPrepareFilter); }

    @Override public void doFilter(PreparationList preparationList, FilterChain filterChain) { // 所有过滤器已经执行完毕,执行study方法 if(pos == studyPrepareFilters.size()) { study.study(); // 否则调用下一个对象的方法 }else { studyPrepareFilters.get(pos++).doFilter(preparationList, filterChain); }

    } }

    然后我们还是实现各个功能,这里需要注意各个实现类需要显式的调用 FilterChain 中的 doFilter()方法:

    洗脸:

    package com.gs.designmodel.chainofduty.special;

import com.gs.designmodel.chainofduty.bad.PreparationList;

/**

  • @author: Gaos
  • @Date: 2023-08-28 20:37 **/ public class WashFaceFilter implements StudyPrepareFilter{ @Override public void doFilter(PreparationList preparationList, FilterChain filterChain) { if(preparationList.isWashFace()) { System.out.println("洗脸"); } filterChain.doFilter(preparationList, filterChain); } }

    洗头:

    package com.gs.designmodel.chainofduty.special;

import com.gs.designmodel.chainofduty.bad.PreparationList;

/**

  • @author: Gaos
  • @Date: 2023-08-28 20:38 **/ public class WashHairFilter implements StudyPrepareFilter{ @Override public void doFilter(PreparationList preparationList, FilterChain filterChain) { if(preparationList.isWashHair()) { System.out.println("洗头"); } filterChain.doFilter(preparationList, filterChain); } }

    吃早饭:

    package com.gs.designmodel.chainofduty.special;

import com.gs.designmodel.chainofduty.bad.PreparationList;

/**

  • @author: Gaos
  • @Date: 2023-08-28 20:39 **/ public class EatBreakfastFilter implements StudyPrepareFilter{ @Override public void doFilter(PreparationList preparationList, FilterChain filterChain) { if(preparationList.isHaveBreakfast()) { System.out.println("吃完早饭"); } filterChain.doFilter(preparationList, filterChain); } }

    测试类(调用方):

    package com.gs.designmodel.chainofduty.special;

import com.gs.designmodel.chainofduty.bad.PreparationList; import com.gs.designmodel.chainofduty.normal.Study;

/**

  • @author: Gaos
  • @Date: 2023-08-28 20:40 **/ public class Test { public static void main(String[] args) { 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 EatBreakfastFilter();
    
    FilterChain filterChain = new FilterChain(study);
    filterChain.addFilter(washFaceFilter);
    filterChain.addFilter(washHairFilter);
    filterChain.addFilter(haveBreakfastFilter);
    
    filterChain.doFilter(preparationList, filterChain);

    } }

    结果:

    洗脸
    吃完早饭
    计划完成,开始学习
    

    可能会奇怪,这种写法如果增加或者减少一个责任链对象不是仍然需要修改客户端代码吗? 但是我们其实将所有的责任链对象都封装到 FilterChain中去了。就像 Servlet、或者过滤器,增加或者减少我们不需要改动代码,我们只需要修改相应的 web.xml文件,或者是注册 bean对象时修改一下,在初始化的时候进行注入,就不需要修改业务层面的代码了。这其实也是 ServletFilter的工作原理,它也是典型的责任链模式。

    4.总结

    当程序需要使用不同方式处理不同种类请求,而且请求类型和顺序预先未知的时候、当必须按照顺序执行多个处理者的时候、如果所需处理者及其顺序必须在运行时进行改变的时候,我们可以使用责任链模式。

    它实现了请求发送者和处理者之前的耦合,使各个责任链对象可以专注于自己的事情,同时可以动态对对象个数、顺序做操作。但对代码结构和设计要求更多了,你不能把链变成一个环,那请求就会一直在里面循环。同时作为客户端来讲你的请求也可能无法达到链尾,或者直到链尾都未被处理。

    参考文章:

    blog.csdn.net/ShuSheng000…

    www.cnblogs.com/xrq730/p/10…

    希望这篇文章对大家有所帮助,您的赞和收藏是对我最大的支持和认可!