前言
来菜鸟这个大家庭 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 作为一个责任对象进行链式调用, 优雅实现, 易复用可扩展.
来源: https://www.cnblogs.com/xrq730/p/10633761.html