设计模式之【职责链模式】
金三银四,表妹拿到大厂offer了...
表妹
我:哇!恭喜恭喜呀!
表妹:真是太不容易了。
我:说说看,怎么不容易呀?
表妹:一二面都顺利通过了,HR面也很顺利,但是最后在谈薪环节,我这边提出更高的薪资,结果又安排多一轮总监面。
我:结果怎么样呢,通过了嘛?
表妹:还好通过啦。
你看,一轮面试,二轮面试,HR面试,后面又根据情况,加了一轮总监面。只有前面的面试通过了,才有机会进入到下一轮,这些面试串成一条链,不是很像我们设计模式中的职责链模式嘛?
使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
我觉得这句话更好理解,“将链中的每一个节点看作是一个对象,每个节点处理的请求均不同,且每个节点内部自动维护了其后继节点对象,当一个请求在链路的头部出发时,会沿着链的路径依次传递给每一个节点对象,直到有对象处理这个请求为止。”---责任链模式原理和示例及其在Spring源码中的运用
我们知道,用户生成的内容可能包含一些敏感的词,比如涉黄、广告、或者是政治敏感词。现在有这么一个需求,就是审核用户生成的内容,如果含有这方面词汇的话,就不允许发布。
我们先来看一下第一种实现方式:
1 public class SensitiveWordFilter { 2 // 如果内容中不包含敏感信息的话,就返回true 3 public boolean filter(Content content) { 4 if (!filterSexyWord(content)) { 5 return false; 6 } 7 if (!filterAdsWord(content)) { 8 return false; 9 } 10 if (!filterPoliticalWord(word)) { 11 return false; 12 } 13 14 return true; 15 } 16 17 private boolean filterSexyWord(Content content) { 18 boolean legal = true; 19 // 判断是否含有黄色信息 20 return legal; 21 } 22 23 private boolean filterAdsWord(Content content) { 24 boolean legal = true; 25 // 判断是否含有广告 26 return legal; 27 } 28 29 private boolean filterPoliticalWord(Content content) { 30 boolean legal = true; 31 // 判断是否含有政治敏感信息 32 return legal; 33 } 34 }
这样写确实是能够实现需求,但是呢,很明显可以看到的是,有太多的if分支了,如果还要判断是否包含隐私信息,非法暴力信息等,那就会有更多的if分支。其次就是违背了开-闭原则,如果新增隐私信息过滤器,不但要在这个类里面新增一个filterPrivacyWord()方法,还要在filter()方法中增加一个if分支。
这时候,职责链模式就派上用场啦~
职责链模式
-
Hanlder:抽象处理者角色,定义一个处理请求的接口,包含抽象处理方法和一个后继处理器。
-
ConcreteHandler:Handler实现类,处理它所负责的请求。
-
Client:向一个链上的具体处理者对象提交请求。
我们应用设计模式,主要是为了应对代码的复杂性,让其满足开闭原则,提高代码的扩展性。
Handler
1 public abstract SensitiveWordFilter { 2 protected SensitiveWordFilter successor; 3 4 public void setSuccessor(SensitiveWordFilter successor) { 5 this.successor = successor; 6 } 7 8 public abstract void doFilter(Content content); 9 }
ConcreteHandler
1 public class SexyWordFilter extends SensitiveWordFilter { 2 @Override 3 public void doFilter(Content content) { 4 boolean legal = true; 5 // 实现判断是否有涉黄敏感信息的逻辑 6 if (legal) { 7 successor.doFilter(content); // 不包含涉黄敏感信息,继续检测其他敏感信息 8 } else { 9 System.out.println("内容不合法,不可以发布!"); 10 } 11 } 12 } 13 14 public class AdsWordFilter extends SensitiveWordFilter { 15 @Override 16 public void doFilter(Content content) { 17 boolean legal = true; 18 // 实现判断是否有广告信息的逻辑 19 if (legal) { 20 successor.doFilter(content); // 不包含广告信息,继续监测其他敏感信息 21 } else { 22 System.out.println("内容不合法,不可以发布!"); 23 } 24 } 25 } 26 27 public class PoliticalWordFilter extends SensitiveWordFilter { 28 @Override 29 public void doFilter(Content content) { 30 boolean legal = true; 31 // 实现判断是否有政治敏感信息的逻辑 32 if (legal) { 33 System.out.println("内容合法,可以发布!"); // 因为PoliticalWordFilter是最后一个节点了,所以,无需再传递到下一个节点 34 } else { 35 System.out.println("内容不合法,不可以发布!"); 36 } 37 } 38 }
Client
public class Demo { public static void main(String[] args) { SensitiveWordFilter sexyWordFilter = new SexyWordFilter(); SensitiveWordFilter adsWordFilter = new AdsWordFilter(); SensitiveWordFilter politicalWordFilter = new PoliticalWordFilter(); sexyWordFilter.setSuccessor(adsWordFilter); adsWordFilter.setSuccessor(politicalWordFilter); sexyWordFilter.doFilter(new Content()); // 从职责链开始调用 } }
你看,我们使用职责链模式之后,把各个敏感词过滤函数继续拆分出来,设计成独立的类,进一步简化了SensitiveWordFilter类,让SensitiveWordFilter类的代码不会过多,过复杂。
其次,当我们要扩展新的过滤算法的时候,比如,我们还需要过滤隐私信息,只需要新增一个Filter类,并且在客户端中将它添加到FilterChain中即可,其他代码完全不用修改。这就遵守了开闭原则。
其实,职责链模式可以细分为两种。
纯与不纯的职责链模式
纯的职责链模式:一个具体处理者角色只能对请求作出两种行为中的一个:一个是自己处理(承担责任),另一个是把责任推给下家。不允许出现某一个具体处理者对象在承担了一部分责任后又将责任向下传的情况。请求在责任链中必须被处理,不能出现无果而终的结局。
不纯的职责链模式:允许某个请求被一个具体处理者部分处理后再向下传递,或者一个具体处理者处理完某请求后,其后继处理器可以继续处理该请求,而且一个请求可以最终不被任何处理者对象接受。
我们上面例子中,使用的就是纯的职责链模式。
职责链模式的优点
-
降低了对象之前的耦合度。
该模式使得一个对象无需知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无需拥有对方的明确信息。
-
增强了系统的可扩展性。
可以根据需要增加新的请求处理类,满足开闭原则。
-
增强了给对象指派职责的灵活性。
当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
-
责任链简化了对象之间的连接。
每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了众多if...else...语句。
-
责任分担。
每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
职责链模式的缺点
-
不能保证每个请求一定被处理。
由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都不能得到处理。
-
对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能受到一定影响。
-
职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
职责链模式的应用场景
职责链模式主要是解耦了请求与处理,用户只需要将请求发送到链上即可,无需关心请求的具体内容和处理细节,请求会自动进行传递直至有节点进行处理。常用在框架开发中,用来实现框架的过滤器、拦截器功能,让框架的使用者不需要修改框架源码的情况下,添加新的过滤拦截功能。除此之外,还适用于以下场景:
-
多个对象可以处理同一个请求,但具体由哪个对象处理则在运行时动态决定。
-
在不明确指定接收者的情况下,向多个对象中的一个提交请求。
-
可以动态指定一组对象的处理请求。
职责链模式在开源代码中的应用
职责链模式最常用来开发框架的过滤器和拦截器,比如RPC中的链式拦截器,Java开发中常用的组件Servlet Filter和Spring Interceptor。我们来看一下Spring Interceptor。它是Spring MVC框架的一部分,由Spring MVC框架来提供实现。客户端发送的请求,会先经过Servlet Filter,然后再经过Spring Interceptor,最后到达具体的业务代码中。整体流程如下图所示:
拦截器 HandlerInterceptor定义了3个方法 preHandle、postHandle、afterCompletion。preHandle()对请求的拦截,postHandle()是对响应的拦截。
1 public interface HandlerInterceptor { 2 //Called after HandlerMapping determined an appropriate handler object, but before HandlerAdapter invokes the handler 3 boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; 4 5 //Called after HandlerAdapter actually invoked the handler, but before the DispatcherServlet renders the view 6 void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception; 7 8 // Callback after completion of request processing, that is, after renderingthe view 9 void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception; 10 11 }
接下来,我们来剖析一下,Spring Interceptor底层是如何实现的。
它是基于职责链模式实现的,其中,HandlerExecutionChain类是职责链模式中的处理器链。它的实现相较于Tomcat中的ApplicationFilterChain来说,逻辑更清晰,不需要使用递归来实现,主要是因为它将请求和响应的拦截工作,拆分到了两个函数中实现。
1 public class HandlerExecutionChain { 2 private final Object handler; 3 private HandlerInterceptor[] interceptors; 4 5 //执行链中拦截器的 preHandle 方法,遇到返回 false,执行 triggerAfterCompletion 方法触发 6 boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { 7 HandlerInterceptor[] interceptors = getInterceptors(); 8 if (!ObjectUtils.isEmpty(interceptors)) { 9 for (int i = 0; i < interceptors.length; i++) { 10 HandlerInterceptor interceptor = interceptors[i]; 11 if (!interceptor.preHandle(request, response, this.handler)) { 12 triggerAfterCompletion(request, response, null); 13 return false; 14 } 15 this.interceptorIndex = i; 16 } 17 } 18 return true; 19 } 20 21 //DispatcherServlet 中 doDispatch 方法调用,调用链中拦截器的 postHandle 方法 22 void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception { 23 HandlerInterceptor[] interceptors = getInterceptors(); 24 if (!ObjectUtils.isEmpty(interceptors)) { 25 for (int i = interceptors.length - 1; i >= 0; i--) { 26 HandlerInterceptor interceptor = interceptors[i]; 27 interceptor.postHandle(request, response, this.handler, mv); 28 } 29 } 30 } 31 32 //执行链中截止到 applyPreHandle 返回 false 的 拦截器的 afterCompletion 方法 33 void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception { 34 HandlerInterceptor[] interceptors = getInterceptors(); 35 if (!ObjectUtils.isEmpty(interceptors)) { 36 for (int i = this.interceptorIndex; i >= 0; i--) { 37 HandlerInterceptor interceptor = interceptors[i]; 38 try { 39 interceptor.afterCompletion(request, response, this.handler, ex); 40 } 41 catch (Throwable ex2) { 42 logger.error("HandlerInterceptor.afterCompletion threw exception", ex2); 43 } 44 } 45 } 46 } 47 }
在Spring框架中,DispatcherServlet的doDispatch()方法来分发请求,它在真正的业务逻辑执行前后,执行HandlerExecutionChain中的applyPreHandle()和applyPostHandle()函数,用来实现拦截的功能。
总结
各人自扫门前雪,莫管他人瓦上霜。
参考
极客时间专栏《设计模式之美》