Fork me on GitHub

【趣味设计模式系列】之【责任链模式】

 


1. 简介

责任链模式(Chain of Responsibility):使多个对象都有机会处理请求,从而避免了请求的发送者接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。

2. 图解

商城新开张,每个订单,可以享受多张优惠券叠加减免

责任链模式

3. 案例实现

类图如下

  • 定义一个优惠券打折抽象类;
  • 抽象类包含一个指向自身的引用nextDiscountFilter,用来把对象串成链,原价计算优惠后的价格方法calculateBySourcePrice;

实现类

  • FullDistcountFliter满200减20元;
  • FirstPurchaseDiscount首次购买减20元;
  • SecondPurchaseDiscountFilter第二件打9折;
  • HolidayDiscountFilter节日一律减5元.

接口与实现类

package com.wzj.chainOfResponsibility.example2; /** * @Author: wzj * @Date: 2019/9/8 9:06 * @Desc: 折扣优惠接口 */ public abstract class DiscountFilter { // 下一个责任链成员 protected DiscountFilter nextDiscountFilter; // 根据原价计算优惠后的价格 public abstract int calculateBySourcePrice(int price); }
package com.wzj.chainOfResponsibility.example2; /** * @Author: wzj * @Date: 2019/9/8 9:07 * @Desc: 满200减20元 */ public class FullDiscountFilter extends DiscountFilter{ public int calculateBySourcePrice(int price) { if (price > 200){ System.out.println("优惠满减20元"); price = price - 20; } if(this.nextDiscountFilter != null) { return super.nextDiscountFilter.calculateBySourcePrice(price); } return price; } }
package com.wzj.chainOfResponsibility.example2; /** * @Author: wzj * @Date: 2019/9/8 16:06 * @Desc: 首次购买减20元 */ public class FirstPurchaseDiscount extends DiscountFilter { public int calculateBySourcePrice(int price) { if (price > 100){ System.out.println("首次购买减20元"); price = price - 20; } if(this.nextDiscountFilter != null) { return super.nextDiscountFilter.calculateBySourcePrice(price); } return price; } }
package com.wzj.chainOfResponsibility.example2; /** * @Author: wzj * @Date: 2019/9/8 16:09 * @Desc: 第二件打9折 */ public class SecondPurchaseDiscountFilter extends DiscountFilter{ public int calculateBySourcePrice(int price) { System.out.println("第二件打9折"); Double balance = price * 0.9; if(this.nextDiscountFilter != null) { return super.nextDiscountFilter.calculateBySourcePrice(balance.intValue()); } return price; } }
package com.wzj.chainOfResponsibility.example2; /** * @Author: wzj * @Date: 2019/9/8 16:02 * @Desc: 节日一律减5元 */ public class HolidayDiscountFilter extends DiscountFilter{ public int calculateBySourcePrice(int price) { if (price > 20){ System.out.println("节日一律减5元"); price = price - 5; } if(this.nextDiscountFilter != null) { return super.nextDiscountFilter.calculateBySourcePrice(price); } return price; } }

测试类

package com.wzj.chainOfResponsibility.example2; /** * @Author: wzj * @Date: 2019/9/8 16:17 * @Desc: */ public class TestDiscountFilter { public static void main(String[] args) { int price = 240; String productStr = String.format("商品清单:苹果、香蕉、桔子, 商品总金额为:%d元.", price); System.out.println(productStr); //声明责任链上的所有节点 FullDiscountFilter fulDF = new FullDiscountFilter(); FirstPurchaseDiscount firstDF = new FirstPurchaseDiscount(); SecondPurchaseDiscountFilter secDF = new SecondPurchaseDiscountFilter(); HolidayDiscountFilter holDF = new HolidayDiscountFilter(); //设置链中的顺序:满减->首购减->第二件减->假日减 fulDF.nextDiscountFilter = firstDF; firstDF.nextDiscountFilter = secDF; secDF.nextDiscountFilter = holDF; holDF.nextDiscountFilter = null; int total = fulDF.calculateBySourcePrice(price); System.out.println(String.format("所有商品优惠价后金额为:%d", total)); } }

执行结果

商品清单:苹果、香蕉、桔子, 商品总金额为:240元. 优惠满减20元 首次购买减20元 第二件打9折 节日一律减5元 所有商品优惠价后金额为:175

4. 应用责任链模式手写过滤器

现在有这样一个场景,程序员张三在某相亲节目中找对象,在第一关和第二关分别被女孩灭灯pass掉,下面分别看看俩姑娘是如何过滤张三的。
小美对话张三

小静对话张三

此场景可以模拟一个http请求如何经过过滤器到服务端,经过每一个过滤器,可以想象为张三相亲过程中经过某个关卡,由于不符合该过滤器的条件被姑娘过滤掉。

第一个版本v1

  • 定义一个请求类和一个响应类MyRequestMyResponse
  • 定义过滤器MyFilter
  • 定义两个过滤器的实现类HeightFliterEducationalBackGroundFilter,分别实现身高过滤和学历过滤;
  • 定义链条MyFilterChain,实现接口MyFilteradd方法实现往过滤链条里添加过滤对象;doFilter方法实现所有过滤对象的过滤操作;

实现代码如下

package com.wzj.chainOfResponsibility.example1.v1; /** * @Author: wzj * @Date: 2019/9/7 20:14 * @Desc: 请求 */ public class MyRequest { String str; }
package com.wzj.chainOfResponsibility.example1.v1; /** * @Author: wzj * @Date: 2019/9/7 20:15 * @Desc: 响应 */ public class MyResponse { String str; }
package com.wzj.chainOfResponsibility.example1.v1; /** * @Author: wzj * @Date: 2019/9/7 20:00 * @Desc: 模拟实现过滤器 */ public interface MyFilter { void doFilter(MyRequest myRequest, MyResponse myResponse); }
package com.wzj.chainOfResponsibility.example1.v1; import java.util.ArrayList; import java.util.List; /** * @Author: wzj * @Date: 2019/9/7 20:36 * @Desc: 责任链 */ public class MyFilterChain implements MyFilter { List<MyFilter> list = new ArrayList<MyFilter>(); public MyFilterChain add(MyFilter myFilter) { list.add(myFilter); return this; } public void doFilter(MyRequest myRequest, MyResponse myResponse) { for(MyFilter f : list ){ f.doFilter(myRequest, myResponse); } } }
package com.wzj.chainOfResponsibility.example1.v1; /** * @Author: wzj * @Date: 2019/9/7 20:20 * @Desc: 身高过滤器 */ public class HeightFliter implements MyFilter { public void doFilter(MyRequest myRequest, MyResponse myResponse) { myRequest.str = myRequest.str.replace("170", "个子有点矮"); myResponse.str += "【妹子挑剔,需要过滤身高】"; } }
package com.wzj.chainOfResponsibility.example1.v1; /** * @Author: wzj * @Date: 2019/9/7 20:33 * @Desc: 教育背景过滤器 */ public class EducationalBackGroundFilter implements MyFilter { public void doFilter(MyRequest myRequest, MyResponse myResponse) { myRequest.str = myRequest.str.replace("学历大专", "学历不高"); myResponse.str += "【妹子挑剔,需要过滤学历】"; } }

测试类

package com.wzj.chainOfResponsibility.example1.v1; /** * @Author: wzj * @Date: 2019/9/7 20:42 * @Desc: 直观的方式处理response, * 并将response的处理放在request的下面 */ public class TestV1 { public static void main(String[] args) { MyRequest myRequest = new MyRequest(); myRequest.str = "张三身高170,学历大专,跪求妹子给个机会认识"; System.out.println("request:" + myRequest.str); MyResponse myResponse = new MyResponse(); myResponse.str = ""; MyFilterChain chain = new MyFilterChain(); chain.add(new HeightFliter()).add(new EducationalBackGroundFilter()); chain.doFilter(myRequest, myResponse); System.out.println("response:" + myResponse.str); } }

结果

request:张三身高170,学历大专,跪求妹子给个机会认识 response:【妹子挑剔,需要过滤身高】【妹子挑剔,需要过滤学历】

现在有如下需求,为更好的模拟一次完整请求,在过滤请求时顺序,响应请求时逆序,有何办法可以做到呢?

第二个版本v2

  • MyRequest类和MyResponse同v1
  • MyFilterChain中加入记录具体责任链对象的下标index,实现记录位置功能;
  • doFilter方法里面实现递归调用,并把当前的链条MyFilterChain作为参数一直向后传递;
    具体实现如下
package com.wzj.chainOfResponsibility.example1.v2; /** * @Author: wzj * @Date: 2019/9/7 20:00 * @Desc: 模拟实现过滤器 */ public interface MyFilter { void doFilter(MyRequest myRequest, MyResponse myResponse, MyFilterChain myFilterChain); }
package com.wzj.chainOfResponsibility.example1.v2; import java.util.ArrayList; import java.util.List; /** * @Author: wzj * @Date: 2019/9/7 20:36 * @Desc: 在MyFilterChain中处理加入位置的记录index */ public class MyFilterChain implements MyFilter { List<MyFilter> list = new ArrayList<MyFilter>(); int index = 0; public MyFilterChain add(MyFilter myFilter) { list.add(myFilter); return this; } public void doFilter(MyRequest myRequest, MyResponse myResponse, MyFilterChain myFilterChain) { if(index == list.size()) return; MyFilter myFilter = list.get(index); index ++; myFilter.doFilter(myRequest, myResponse, myFilterChain); } }
package com.wzj.chainOfResponsibility.example1.v2; /** * @Author: wzj * @Date: 2019/9/7 20:20 * @Desc: 身高过滤器 */ public class HeightFliter implements MyFilter { public void doFilter(MyRequest myRequest, MyResponse myResponse, MyFilterChain myFilterChain) { myRequest.str = myRequest.str.replace("170", "个子有点矮"); myFilterChain.doFilter(myRequest, myResponse, myFilterChain); myResponse.str += "【妹子挑剔,需要过滤身高】"; } }
package com.wzj.chainOfResponsibility.example1.v2; /** * @Author: wzj * @Date: 2019/9/7 20:33 * @Desc: 教育背景过滤器 */ public class EducationalBackGroundFilter implements MyFilter { public void doFilter(MyRequest myRequest, MyResponse myResponse, MyFilterChain myFilterChain) { myRequest.str = myRequest.str.replace("学历大专", "学历不高"); myFilterChain.doFilter(myRequest, myResponse, myFilterChain); myResponse.str += "【妹子挑剔,需要过滤学历】"; } }

测试类

package com.wzj.chainOfResponsibility.example1.v2; /** * @Author: wzj * @Date: 2019/9/7 20:42 * @Desc: 过滤请求时顺序,响应请求时逆序,在MyFilterChain中处理加入位置的记录, * 同时在MyFilter中加入第三个参数MyFilterChain,让链条递归实现倒序 */ public class TestV2 { public static void main(String[] args) { MyRequest myRequest = new MyRequest(); myRequest.str = "张三身高170,学历大专,跪求妹子给个机会认识"; System.out.println("request:" + myRequest.str); MyResponse myResponse = new MyResponse(); myResponse.str = ""; MyFilterChain chain = new MyFilterChain(); chain.add(new HeightFliter()).add(new EducationalBackGroundFilter()); chain.doFilter(myRequest, myResponse, chain); System.out.println("response:" + myResponse.str); } }

结果中显示先过滤学历,后过滤身高。

request:张三身高170,学历大专,跪求妹子给个机会认识 response:【妹子挑剔,需要过滤学历】【妹子挑剔,需要过滤身高】

由于MyFilterChain在做doFilter时,始终传递的是当前MyFilterChain的同一个实例,故可以简化MyFilterChain

第三个版本v3

  • MyFilterChain去除MyFilter接口;
  • doFilter方法去除第三个参数MyFilterChain
    具体实现如下
package com.wzj.chainOfResponsibility.example1.v3; import java.util.ArrayList; import java.util.List; /** * @Author: wzj * @Date: 2019/9/7 20:36 * @Desc: 过滤器完全模式,去掉实现接口,并将doFilter方法中的chain */ public class MyFilterChain{ List<MyFilter> list = new ArrayList<MyFilter>(); int index = 0; public MyFilterChain add(MyFilter myFilter) { list.add(myFilter); return this; } public void doFilter(MyRequest myRequest, MyResponse myResponse) { if(index == list.size()) return; MyFilter myFilter = list.get(index); index ++; myFilter.doFilter(myRequest, myResponse, this); } }
package com.wzj.chainOfResponsibility.example1.v3; /** * @Author: wzj * @Date: 2019/9/7 20:20 * @Desc: 身高过滤器 */ public class HeightFliter implements MyFilter { public void doFilter(MyRequest myRequest, MyResponse myResponse, MyFilterChain myFilterChain) { myRequest.str = myRequest.str.replace("170", "个子有点矮"); myFilterChain.doFilter(myRequest, myResponse); myResponse.str += "【妹子挑剔,需要过滤身高】"; } }
package com.wzj.chainOfResponsibility.example1.v3; /** * @Author: wzj * @Date: 2019/9/7 20:33 * @Desc: 教育背景过滤器 */ public class EducationalBackGroundFilter implements MyFilter { public void doFilter(MyRequest myRequest, MyResponse myResponse, MyFilterChain myFilterChain) { myRequest.str = myRequest.str.replace("学历大专", "学历不高"); myFilterChain.doFilter(myRequest, myResponse); myResponse.str += "【妹子挑剔,需要过滤学历】"; } }

测试类

package com.wzj.chainOfResponsibility.example1.v3; /** * @Author: wzj * @Date: 2019/9/7 20:42 * @Desc: 过滤器完全模式 */ public class TestV3 { public static void main(String[] args) { MyRequest myRequest = new MyRequest(); myRequest.str = "张三身高170,学历大专,跪求妹子给个机会认识"; System.out.println("request:" + myRequest.str); MyResponse myResponse = new MyResponse(); myResponse.str = ""; MyFilterChain chain = new MyFilterChain(); chain.add(new HeightFliter()).add(new EducationalBackGroundFilter()); chain.doFilter(myRequest, myResponse); System.out.println("response:" + myResponse.str); } }

结果

request:张三身高170,学历大专,跪求妹子给个机会认识 response:【妹子挑剔,需要过滤学历】【妹子挑剔,需要过滤身高】

5. 过滤器源码分析

在web项目中经常需要配置满足我们需要的各种过滤器filter,来过滤满足我们自定义信息,不烦看一下tomcat中的源码中是如何做到的,下面以tomcat8.5.37的源码来分析具体实现。
找到ApplicationFilterFactory类,其中的createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet)方法创建了责任链,

public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) { // 如何没有servlet执行,返回null if (servlet == null) return null; // 创建和初始化过滤器链对象 ApplicationFilterChain filterChain = null; if (request instanceof Request) { Request req = (Request) request; if (Globals.IS_SECURITY_ENABLED) { filterChain = new ApplicationFilterChain(); } else { filterChain = (ApplicationFilterChain) req.getFilterChain(); if (filterChain == null) { filterChain = new ApplicationFilterChain(); req.setFilterChain(filterChain); } } } else { filterChain = new ApplicationFilterChain(); } filterChain.setServlet(servlet); filterChain.setServletSupportsAsync(wrapper.isAsyncSupported()); // 获取上下中的过滤器的映射集合 StandardContext context = (StandardContext) wrapper.getParent(); FilterMap filterMaps[] = context.findFilterMaps(); // If there are no filter mappings, we are done if ((filterMaps == null) || (filterMaps.length == 0)) return (filterChain); // 获取匹配的过滤器映射信息 DispatcherType dispatcher = (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR); String requestPath = null; Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR); if (attribute != null){ requestPath = attribute.toString(); } String servletName = wrapper.getName(); // 对过滤器链添加对应的路径匹配过滤器 for (int i = 0; i < filterMaps.length; i++) { if (!matchDispatcher(filterMaps[i] ,dispatcher)) { continue; } if (!matchFiltersURL(filterMaps[i], requestPath)) continue; ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMaps[i].getFilterName()); if (filterConfig == null) { // FIXME - log configuration problem continue; } filterChain.addFilter(filterConfig); } // 添加匹配servlet name的过滤器 for (int i = 0; i < filterMaps.length; i++) { if (!matchDispatcher(filterMaps[i] ,dispatcher)) { continue; } if (!matchFiltersServlet(filterMaps[i], servletName)) continue; ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMaps[i].getFilterName()); if (filterConfig == null) { // FIXME - log configuration problem continue; } filterChain.addFilter(filterConfig); } // 返回过滤器责任链 return filterChain; }

执行过滤器中的doFilter方法,会调用一个 internalDoFilter() 方法

public final class ApplicationFilterChain implements FilterChain { ...... /** * 维护当前过滤器链的位置信息 */ private int pos = 0; /** * 当前过滤器的长度 */ private int n = 0; ...... @Override public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if( Globals.IS_SECURITY_ENABLED ) { final ServletRequest req = request; final ServletResponse res = response; try { java.security.AccessController.doPrivileged( new java.security.PrivilegedExceptionAction<Void>() { @Override public Void run() throws ServletException, IOException { internalDoFilter(req,res); return null; } } ); } catch( PrivilegedActionException pe) { Exception e = pe.getException(); if (e instanceof ServletException) throw (ServletException) e; else if (e instanceof IOException) throw (IOException) e; else if (e instanceof RuntimeException) throw (RuntimeException) e; else throw new ServletException(e.getMessage(), e); } } else { internalDoFilter(request,response); } } private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { // 如果有下一个,则调用下一个过滤器 if (pos < n) { ApplicationFilterConfig filterConfig = filters[pos++]; try { Filter filter = filterConfig.getFilter(); if (request.isAsyncSupported() && "false".equalsIgnoreCase( filterConfig.getFilterDef().getAsyncSupported())) { request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE); } if( Globals.IS_SECURITY_ENABLED ) { final ServletRequest req = request; final ServletResponse res = response; Principal principal = ((HttpServletRequest) req).getUserPrincipal(); Object[] args = new Object[]{req, res, this}; SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal); } else { /// 调用Filter的doFilter()方法 , doFilter 又会调用 internalDoFilter,一直递归下去, 直到调用完所有的过滤器 filter.doFilter(request, response, this); } } catch (IOException | ServletException | RuntimeException e) { throw e; } catch (Throwable e) { e = ExceptionUtils.unwrapInvocationTargetException(e); ExceptionUtils.handleThrowable(e); throw new ServletException(sm.getString("filterChain.filter"), e); } return; } // 从最后一个过滤器开始调用 try { if (ApplicationDispatcher.WRAP_SAME_OBJECT) { lastServicedRequest.set(request); lastServicedResponse.set(response); } if (request.isAsyncSupported() && !servletSupportsAsync) { request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE); } // Use potentially wrapped request from this point if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse) && Globals.IS_SECURITY_ENABLED ) { final ServletRequest req = request; final ServletResponse res = response; Principal principal = ((HttpServletRequest) req).getUserPrincipal(); Object[] args = new Object[]{req, res}; SecurityUtil.doAsPrivilege("service", servlet, classTypeUsedInService, args, principal); } else { servlet.service(request, response); } } catch (IOException | ServletException | RuntimeException e) { throw e; } catch (Throwable e) { e = ExceptionUtils.unwrapInvocationTargetException(e); ExceptionUtils.handleThrowable(e); throw new ServletException(sm.getString("filterChain.servlet"), e); } finally { if (ApplicationDispatcher.WRAP_SAME_OBJECT) { lastServicedRequest.set(null); lastServicedResponse.set(null); } } } }

6. 责任链模式总结

优点

  • 将请求发送者和接收处理者分开。请求者可以不用知道是谁处理的,处理者可以不用知道请求的全貌,两者解耦,提高系统的灵活性。

缺点

  • 性能问题,每个请求都是从链头遍历到链尾,特别是在链比较长的时候,性能是一个非常大的问题;
  • 调试不很方便,特别是链条比较长,类似递归的方式,调试的时候逻辑可能比较复杂。

注意事项

  • 链中节点数量需要控制,避免出现超长链的情况,一般的做法是在Handler中设置一个最大节点数量,在setNext方法中判断是否已经是超过其阈值,超过则不允许该链建立,避免无意识地破坏系统性能。

__EOF__

本文作者小猪爸爸
本文链接https://www.cnblogs.com/father-of-little-pig/p/11488898.html
关于博主:不要为了技术而技术,总结分享技术,感恩点滴生活!
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   小猪爸爸  阅读(1361)  评论(0编辑  收藏  举报
编辑推荐:
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示