责任链模式的定义与特点
责任链模式的定义:使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。
标准的责任链模式,个人总结下来有如下几个特点:
用一张图表示以下使用了责任链模式之后的架构:
也就是说,责任链模式满足了请求发送者与请求处理者之间的松耦合,抽象非核心的部分,以链式调用的方式对请求对象进行处理。
这么说不明白?那么下面通过实际例子让你明白。
不使用责任链模式
为什么要使用责任链模式,那么我们得知道不使用责任链模式有什么坏处,然后通过使用责任链模式如何将代码优化。
现在有一个场景:小明要去上学,妈妈给小明列了一些上学前需要做的清单(洗头、吃早饭、洗脸),小明必须按照妈妈的要求,把清单上打钩的事情做完了才可以上学。
首先我们定义一个准备列表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("我可以去上学了!"); } }
这个例子实现了我们的需求,但是不够优雅,我们的主流程是学习,但是把要准备做的事情这些动作耦合在学习中,这样有两个问题:
最糟糕的写法,只是为了满足功能罢了,违背开闭原则,即当我们扩展功能的时候需要去修改主流程,无法做到对修改关闭、对扩展开放。
使用责任链模式
接着看一下使用责任链模式的写法,既然责任链模式的特点是“链上的每个对象都持有下一个对象的引用”,那么我们就这么做。
先抽象出一个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方法,只需要修改调用方即可。
但是这种写法好吗?个人认为这种写法虽然符合开闭原则,但是两个明显的缺点对客户端并不友好:
为此,我们来个终极版的、升级版的责任链模式。
升级版责任链模式
上面我们写了一个责任链模式,这种是一种初级的符合责任链模式的写法,最后也写了,这种写法是有明显的缺点的,那么接着我们看一下升级版的责任链模式如何写,解决上述问题。
以下的写法也是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作为一个责任对象进行链式调用,优雅实现,易复用可扩展。