职责链模式
1、简介
定义:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
本质:分离职责,动态组合。
分离职责:分离职责是前提,只有先把复杂功能分开,拆分成很多的步骤和小的功能处理,然后才能合理规划和定义职责类。
动态组合:动态组合是精华,因为要实现请求对象和处理对象的解耦,请求对象不知道谁才是真正的处理对象。
英文:Chain of Responsibility
类型:行为型
2、类图及组成
(引)类图:
组成:
Handler(抽象处理类):抽象处理类中主要包含一个指向下一处理类的成员变量successor和一个处理请求的方法handRequest,handRequest方法的主要主要思想是,如果满足处理的条件,则有本处理类来进行处理,否则由successor来处理。
ConcreteHandler(具体处理类):在这个类里面,实现对在它职责范围内请求的处理,如果不处理,就继续转发请求给后继者。
Client(客户端):向链上的具体处理者对象提交请求,让职责链负责处理。
代码结构:
/** * 抽象职责类 */ public abstract class Handler { /** * 持有后继的职责对象 */ protected Handler successor; /** * 设置后继的职责对象 * @param successor 后继的职责对象 */ public void setSuccessor(Handler successor) { this.successor = successor; } /** * 示意处理请求的方法,虽然这个示意方法是没有传入参数, * 但实际是可以传入参数的,根据具体需要来选择是否传递参数 */ public abstract void handleRequest(); } /** * 具体的职责对象,用来处理请求 */ public class ConcreteHandlerA extends Handler { public void handleRequest() { //根据某些条件来判断是否属于自己处理的职责范围 //判断条件比如:从外部传入的参数,或者这里主动去获取的外部数据, //如从数据库中获取等,下面这句话只是个示意 boolean someCondition = false; if(someCondition){ //如果属于自己处理的职责范围,就在这里处理请求 //具体的处理代码 System.out.println("ConcreteHandlerA handle request"); }else{ //如果不属于自己处理的职责范围,那就判断是否还有后继的职责对象 //如果有,就转发请求给后继的职责对象 //如果没有,什么都不做,自然结束 if(this.successor!=null){ this.successor.handleRequest(); } } } } /** * 具体的职责对象,用来处理请求 */ public class ConcreteHandlerB extends Handler { public void handleRequest() { //根据某些条件来判断是否属于自己处理的职责范围 //判断条件比如:从外部传入的参数,或者这里主动去获取的外部数据, //如从数据库中获取等,下面这句话只是个示意 boolean someCondition = false; if(someCondition){ //如果属于自己处理的职责范围,就在这里处理请求 //具体的处理代码 System.out.println("ConcreteHandlerB handle request"); }else{ //如果不属于自己处理的职责范围,那就判断是否还有后继的职责对象 //如果有,就转发请求给后继的职责对象 //如果没有,什么都不做,自然结束 if(this.successor!=null){ this.successor.handleRequest(); } } } } /** * 职责链的客户端,这里只是个示意 */ public class Client { public static void main(String[] args) { //先要组装职责链 Handler h1 = new ConcreteHandlerA(); Handler h2 = new ConcreteHandlerB(); h1.setSuccessor(h2); //然后提交请求 h1.handleRequest(); } }
3、引入实例
这里模拟一下肯德基店铺接单的简单逻辑:
package com.designpattern.ChainOfResponsibility; /** * 抽象职责类 -- 肯德基 * @author Json<<json1990@foxmail.com>> */ public abstract class KFC { /** * 持有后继的职责对象 */ protected KFC successor; /** * 设置后继的职责对象 * @param successor 后继的职责对象 */ public void setSuccessor(KFC successor) { this.successor = successor; } /** * 处理接单 * @param _x 下单地点横坐标,用于判断距离 * @param _y 下单地点纵坐标,用于判断距离 */ public abstract void handleRequest(int _x,int _y); }
来三个具体的分店,这里就列举几个示意:
package com.designpattern.ChainOfResponsibility; import com.designpattern.utils.LocationUtil; /** * 肯德基 -- 北京酒仙桥分店 * @author Json<<json1990@foxmail.com>> */ public class JiuXianQiaoSubbranch extends KFC{ //最大配送距离 private int maxDeliveryDistance = 500; private int x = 800;//分店的横坐标,用于判断距离 private int y = 200;//分店的纵坐标,用于判断距离 public void handleRequest(int _x,int _y) { //在配送范围内,可配送 if(LocationUtil.getDistance(_x, _y, x, y) <= maxDeliveryDistance){ //如果属于自己处理的职责范围,就在这里处理请求 //具体的处理代码 System.out.println("酒仙桥分店已接单,老司机马上派送"); }else{ //如果不属于自己处理的职责范围,那就判断是否还有后继的职责对象 //如果有,就转发请求给后继的职责对象 //如果没有,什么都不做,自然结束 if(this.successor!=null){ this.successor.handleRequest(_x,_y); } } } }
package com.designpattern.ChainOfResponsibility; import com.designpattern.utils.LocationUtil; /** * 肯德基 -- 北京望京分店 * @author Json<<json1990@foxmail.com>> */ public class WangJingSubbranch extends KFC { //最大配送距离 private int maxDeliveryDistance = 500; private int x = 800;//分店的横坐标,用于判断距离 private int y = 600;//分店的纵坐标,用于判断距离 public void handleRequest(int _x,int _y) { //在配送范围内,可配送 if(LocationUtil.getDistance(_x, _y, x, y) <= maxDeliveryDistance){ //如果属于自己处理的职责范围,就在这里处理请求 //具体的处理代码 System.out.println("望京分店已接单,老司机马上派送"); }else{ //如果不属于自己处理的职责范围,那就判断是否还有后继的职责对象 //如果有,就转发请求给后继的职责对象 //如果没有,什么都不做,自然结束 if(this.successor!=null){ this.successor.handleRequest(_x,_y); } } } }
package com.designpattern.ChainOfResponsibility; import com.designpattern.utils.LocationUtil; /** * 肯德基 -- 北京东坝分店 * @author Json<<json1990@foxmail.com>> */ public class DongBaSubbranch extends KFC { //最大配送距离 private int maxDeliveryDistance = 500; private int x = 800;//分店的横坐标,用于判断距离 private int y = 800;//分店的纵坐标,用于判断距离 public void handleRequest(int _x,int _y) { //在配送范围内,可配送 if(LocationUtil.getDistance(_x, _y, x, y) <= maxDeliveryDistance){ //如果属于自己处理的职责范围,就在这里处理请求 //具体的处理代码 System.out.println("东坝分店已接单,老司机马上派送"); }else{ //如果不属于自己处理的职责范围,那就判断是否还有后继的职责对象 //如果有,就转发请求给后继的职责对象 //如果没有,什么都不做,自然结束 if(this.successor!=null){ this.successor.handleRequest(_x,_y); } } } }
辅助工具类:
package com.designpattern.utils; /** * 坐标工具类 -- 简单计算坐标间距 * @author Json<<json1990@foxmail.com>> */ public class LocationUtil { //计算坐标之间的距离 public static double getDistance(int x1,int y1,int x2,int y2){ return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); } }
下面客户端测试:
package com.designpattern.ChainOfResponsibility; /** * 客户端测试 * @author Json<<json1990@foxmail.com>> */ public class Client { public static void main(String[] args) { //先要组装职责链 JiuXianQiaoSubbranch jiuxianqiao = new JiuXianQiaoSubbranch(); WangJingSubbranch wangjing = new WangJingSubbranch(); DongBaSubbranch dongba = new DongBaSubbranch(); jiuxianqiao.setSuccessor(wangjing); wangjing.setSuccessor(dongba); //张三下单 ,定一份鸡腿堡套餐 jiuxianqiao.handleRequest(900,200); } }
结果:
酒仙桥分店已接单,老司机马上派送
4、优缺点
优点:
请求者和接收者松散耦合:在职责链模式里面,请求者并不知道接收者是谁,也不知道具体如何处理,请求者只是负责向职责链发出请求就可以了。而每个职责对象也不用管请求者或者是其它的职责对象,只负责处理自己的部分,其它的就交由其它的职责对象去处理。也就是说,请求者和接收者是完全解耦的。
动态组合职责:职责链模式会把功能处理分散到单独的职责对象里面,然后在使用的时候,可以动态组合职责形成职责链,从而可以灵活的给对象分配职责,也可以灵活的实现和改变对象的职责。
简化了对象:请求处理对象仅需维持一个指向其后继者的引用,而不需要维持它对所有的候选处理者的引用,可简化对象的相互连接。
增加新的请求处理类很方便:在系统中增加一个新的具体请求处理者时无须修改原有系统的代码,只需要在客户端重新建链即可,从这一点来看是符合“开闭原则”的。
缺点:
产生很多细粒度对象:职责链模式会把功能处理分散到单独的职责对象里面,也就是每个职责对象只是处理一个方面的功能,要把整个业务处理完,需要大量的职责对象的组合,这会产生大量的细粒度职责对象。
不一定能被处理:职责链模式的每个职责对象只负责自己处理的那一部分,因此可能会出现某个请求,把整个链传递完了,都没有职责对象处理它。这就需要在使用职责链模式的时候注意,需要提供默认的处理,并且注意构建的链的有效性。
可能造成循环调用:如果建链不当,可能会造成循环调用,将导致系统陷入死循环。
调试不方便:对于比较长的职责链,请求的处理可能涉及到多个处理对象,在进行代码调试时不太方便,有碍于除错。
5、应用场景
1、有多个对象可以处理同一个请求,但是具体由哪个对象来处理该请求,是运行时刻动态确定的。这种情况可以使用职责链模式,把处理请求的对象实现成为职责对象,然后把它们构成一个职责链,当请求在这个链中传递的时候,具体由哪个职责对象来处理,会在运行时动态判断。
2、你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求的话,可以使用职责链模式,职责链模式实现了请求者和接收者之间的解耦,请求者不需要知道究竟是哪一个接收者对象来处理了请求。
3、想要动态指定处理一个请求的对象集合,可以使用职责链模式,职责链模式能动态的构建职责链,也就是动态的来决定到底哪些职责对象来参与到处理请求中来,相当于是动态指定了处理一个请求的职责对象集合。
4、使用if…else…语句,代码看上去很糟糕时,就可以使用责任链模式实现。
开发中常见场景:
1、Java的异常机制就是一个责任链模式,一个try可以对应多个catch。如果某一个catch不匹配,则跳到下一个catch中。
2、JavaScript语言中的事件的冒泡和捕获机制。
3、Servlet开发中,过滤器的链式处理。
4、Tomcat中的Filter就是使用了责任链模式,还有Tomcat中Valve链...
......
6、职责链模式的纯与不纯
纯的职责链模式:
一个纯的职责链模式要求一个具体处理者对象只能在两个行为中选择一个:要么承担全部责任,要么将责任推给下家,不允许出现某一个具体处理者对象在承担了一部分或全部责任后又将责任向下传递的情况。而且在纯的职责链模式中,要求一个请求必须被某一个处理者对象所接收,不能出现某个请求未被任何一个处理者对象处理的情况。
不纯的职责链模式:
在一个不纯的职责链模式中允许某个请求被一个具体处理者部分处理后再向下传递,或者一个具体处理者处理完某请求后其后继处理者可以继续处理该请求,而且一个请求可以最终不被任何处理者对象所接收。
7、责任链模式与状态模式对比
状态模式也好,责任链模式也罢,都能解耦和优化大量的逻辑判断,如:if…else…语句。
责任链模式使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象练成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。各个责任类不知道也没必要知道下一个责任对象是谁!由客户端统一设置顺序和谁连接到链条,谁不连接到链条…… 可以看出,职责链在client(客户端)连接,职责链模式要比状态模式灵活很多。
但是,这时候有人要问,既然他们都可以解决逻辑判断的分支过多的问题,那么,是不是责任链模式比状态模式好呢?
职责链模式过于灵活,在客户端使用时,需要客户端去确定下一个对象是谁,一些列的set操作…… 在多次设置的时候很容易出问题。
另外状态模式是一个对象的内在状态发生改变,而职责链模式是多个对象之间的改变,这也说明他们两个模式处理的情况不同。
小结:
个人认为,职责链模式与状态模式的最大的不同是设置自己的下一级的问题上,状态模式是在类的设计阶段就定好的,不能在客户端改变,而职责链的下一级是在客户端自己来确定的。
状态模式:是让各个状态对象自己知道其下一个处理的对象是谁,即在编译时便设定。相当于If ,else-if,else-if……, 设计思路是把逻辑判断转移到各个State类的内部实现(相当于If,else If),执行时客户端通过调用环境—Context类的方法来间接执行状态类的行为,客户端不直接和状态交互。
职责链模式:各个对象并不指定其下一个处理的对象到底是谁,只有在客户端才设定某个类型的链条,请求发出后穿越链条,直到被某个职责类处理或者链条结束。设计思路是把各个业务逻辑判断封装到不同职责类,且携带下一个职责的对应引用,但不像状态模式那样需要明确知道这个引用指向谁,而是在客户端设置链接方式或者过程。使用时,向链的第一个子类的执行方法传递参数就可以。客户端去通过环境类调用责任链,全自动运转起来。
8、总结
职责链模式通过建立一条链来组织请求的处理者,请求将沿着链进行传递,请求发送者无须知道请求在何时、何处以及如何被处理,实现了请求发送者与处理者的解耦。在软件开发中,如果遇到有多个对象可以处理同一请求时可以应用职责链模式,例如在Web应用开发中创建一个过滤器(Filter)链来对请求数据进行过滤,在工作流系统中实现公文的分级审批等等,使用职责链模式可以较好地解决此类问题。
另外:责任链模式其实就是一个灵活版的if…else…语句,它就是将这些判定条件的语句放到了各个处理类中,这样做的优点是比较灵活了,但同样也带来了风险,比如设置处理类前后关系时,一定要特别仔细,搞对处理类前后逻辑的条件判断关系,并且注意不要在链中出现循环引用的问题。