责任链模式的学习与应用

作者: 李龙杰 | 来源:发表于2017-10-15 19:02 被阅读29次

    定义

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

    场景

    员工想要请假,在OA系统中需要审批.
    对于不同的请假天数,审批人是不一样的.小于3天,组长审批,大于三天小于7天,主管审批,大于7天小于15天CTO审批.

    典型实现

        public Response handle(Request req) {
            if (req.day < 0 || req.day > 15) {
                throw new IllegalArgumentException();
            }
            if (req.day <= 3) {
                return new Leader().handle(req);
            } else if (req.day <= 7) {
                return new Director().handle(req);
            } else {
                return new CTO().handle(req);
            }
        }
    

    实际场景可能要比这个复杂一些,比如Leader,Director,CTO可能是通过注入而不是new的.

    目前的类图:

    优点:

    1. 代码清晰明了.

    缺点:

    1. 耦合度高,客户端代码依赖所有处理类. 如果后续想要继续添加处理类,就需要继续添加else if.
    2. 顺序也是定死的.如果要改顺序.就必须重新修改.

    改进[责任链]

    既然我们的所有处理类都实现了IHandler接口.那我们的执行流程在编译层面就是标准化的.那我们能不能通过链接各个处理类的方式,让Client依赖的实现最小呢?当然可以.和链表类似.我们可以构造一个处理器链.只需要在IHandler里添加setNext()方法即可.

    改进后代码

    IHandler实现类:

    class Leader implements IHandler {
    
        private IHandler next;
    
        public Response handle(Request req) {
            if (req.getDay() <= 3) {
                return new Response("Leader audit");
            }
            return this.next.handle(req);
        }
    
        @Override
        public void setNext(IHandler handler) {
            this.next = handler;
        }
    }
    

    其他类似,就不贴了.

    业务代码:

        public Response handle(Request req) {
            if (req.day < 0 || req.day > 15) {
                throw new IllegalArgumentException();
            }
            return handler.handle(req);
        }
    

    客户端代码:

        public static void main(String[] args) {
            IHandler handler = new Leader();
            IHandler director = new Director();
            IHandler cto = new CTO();
            handler.setNext(director);
            director.setNext(cto);
            CORDemo01 demo = new CORDemo01();
            demo.handler = handler;
            Response response = demo.handle(new Request(6));
            System.out.println(response.getMessage());
        }
    

    改进后的类图

    优点

    1. 不再依赖所有实现.而是只依赖第一个实现.而其他的实现通过链接的方式进行组装.
    2. 我们已经可以通过某些方式动态的组装我们的链了.

    总结

    纯粹的责任链模式这样就介绍完了.
    从传统的编程方式过度到责任链模式.实际上还是很容易理解了.为的就是减少调用类与处理类之间的耦合.
    另外值得注意的是,纯粹的责任链模式,只有一个处理器进行处理.而其他的并不参与执行.
    而我们在实际使用的场景中,比如过滤器,则是多个处理器都会参与执行.
    下面就讨论一下.责任链的几个变种.

    责任链模式列表方式实现

    我们看到纯的责任链模式中,使用了链表的形式.并且暴露了第一个执行单元.
    这种链表的方式在组装的时候如果有很多个节点,将十分繁琐.对于这一部分,有一种常见的优化方案.
    是将所有的处理器按顺序放到一个List中进行处理.代码如下

    IHandler实现类:

    class Leader implements IHandler {
    
        public Response handle(Request req) {
            if (req.getDay() <= 3) {
                return new Response("Leader audit");
            }
            return null;
        }
    }
    

    其他类似,就不贴了.

    HandlerChain

    class HandlerChain {
    
        private List<IHandler> processList = Lists.newArrayList();
    
        public HandlerChain addHandler(IHandler handler) {
            if (handler != null) {
                processList.add(handler);
            }
            // 链式调用.
            return this;
        }
    
        public void setProcessList(List<IHandler> processList) {
            this.processList = processList;
        }
    
        public Response process(Request req) {
            for (IHandler handler : processList) {
                Response response = handler.handle(req);
                if (response == null) {
                    continue;
                }
                return response;
            }
            throw new IllegalArgumentException();
        }
    }
    

    优点

    1. 通过这种方式,我们可以较为轻松的组装我们的处理链.
    2. 通过setProcessList方法.我们也可以动态的更改处理流程.

    不纯粹的责任链

    过滤器

    不纯粹的责任链是指,进入链之后,并不是只有一个处理器才能执行.而是所有的处理器都可能参与执行.
    典型的场景就是过滤器

    下面我们简单实现一个过滤器.我们有一个List的String.我们将其中长度大于1,首字母是w的字符串都挑选出来.如果用过Java8的朋友肯定知道,使用stream表达式,filter就可以很容易的实现这个需求.但是实际场景中,可能要比这个复杂的多.这里只是举个简单的例子.来说明这种不纯粹的责任链的实现.实现方式采取两种.
    传统方式.利用Java8 Function的antThen组装多个Function的方式实现过滤器.

    • 传统方式

      接口类

      public interface IFilter {
          List<String> filter(List<String> toFilter);
      }   
      

      组装类

      class FilterChain {
      
          private List<IFilter> filterList;
      
          public List<String> process(List<String> toFilter) {
              for (IFilter filter : filterList) {
                  toFilter = filter.filter(toFilter);
              }
              return toFilter;
          }
      }
      

      两个实现类

      class LengthFilter implements IFilter {
          @Override
          public List<String> filter(List<String> toFilter) {
              List<String> result = Lists.newArrayList();
              for (String str : toFilter) {
                  if (StringUtils.isNotBlank(str) && str.length() > 1) {
                      result.add(str);
                  }
              }
              return result;
          }
      }
      
      class StartCharFilter implements IFilter {
          @Override
          public List<String> filter(List<String> toFilter) {
              List<String> result = Lists.newArrayList();
              for (String str : toFilter) {
                  if (StringUtils.isNotBlank(str) && str.startsWith("w")) {
                      result.add(str);
                  }
              }
              return result;
          }
      }
      
      

      值得注意的是有很多模板代码.这些模板代码在传统方式下,可以通过模板模式进行简化.

    • Java8 Function方式

      public class HandlerChain {
      
          private static UnaryOperator<List<String>> filterByLength =
                  (List<String> text) -> text.stream().filter(item -> item.length() > 1).collect(Collectors.toList());
      
          private static UnaryOperator<List<String>> spellCheckerProcessing =
                  (List<String> text) -> text.stream().filter(item -> item.startsWith("w")).collect(Collectors.toList());
      
          private static Function<List<String>, List<String>> filterChain =
                  filterByLength.andThen(spellCheckerProcessing);
      
          public static List<String> filter(List<String> input) {
              return filterChain.apply(input);
          }
      }
      
      

    当然在这个需求下,你也可以直接使用stream.将两个filter连接起来.
    但是如果你考虑纯粹的责任链模式.上面的Java8 Funciton的方式.确实可以节省很多代码和类.

    拦截器

    Servlet,Struts2,Spring MVC,都有拦截器.而这些拦截器的实现.则也是不纯粹的责任链的一种.因为所有的拦截器都会执行一遍.如果确认拦截,则直接返回.如果通过,则继续执行.看起来和过滤器很像.

    但是拦截器的功能不止于此,拦截器不仅支持preAction操作,还支持postAction操作.我们想要讨论的就是这种设计是如何编码实现的.

    接口定义

    public interface IInterceptor {
    
        boolean before();
    
        void after();
    }
    

    拦截器与目标对象的组装类

    public class MainExecuteProxy {
    
        private Object target;
    
        private List<IInterceptor> interceptorList = Lists.newArrayList();
    
        public MainExecuteProxy addInterceptor(IInterceptor interceptor) {
            interceptorList.add(interceptor);
            return this;
        }
    
        public String execute() {
            return process(interceptorList.iterator(), target);
        }
    
        private String process(Iterator<IInterceptor> interceptorIterator, Object target) {
            if (interceptorIterator.hasNext()) {
                IInterceptor interceptor = interceptorIterator.next();
                boolean before = interceptor.before();
                if (!before) {
                    return "执行失败";
                }
                String res = process(interceptorIterator, target);
                interceptor.after();
                return res;
            } else {
                // do action.
                System.out.println("real action");
                return "执行成功";
            }
        }
    }
    
    

    代码分析

    MainExecuteProxy类里需要有一个目标对象,用来执行目标方法.而interceptorList就是我们之前讲到的责任链.那这种拦截是怎么实现的呢.主要的逻辑就是process方法.我们的逻辑很简单.就是当我们的所有before方法都为true的时候执行目标对象的方法.当有一个失败的时候,就立马拦截.而在拦截并且执行完目标方法后,我们需要在一个一个的反向执行after方法.因为before/after是配对的.

    为了达到这种目的,我们使用了递归.原因就是递归是可以保留递归现场的.所以的方法在调用的时候进入到方法栈,先进去的在下面,后进去的在上面.所以当我们在执行完目标方法一层一层返回的时候,也是后调用的先执行after.达到了效果.

    Client代码

    public class Main {
    
        public static void main(String[] args) {
            MainExecuteProxy executeProxy = new MainExecuteProxy();
    
            String result = executeProxy.addInterceptor(new IInterceptor() {
                @Override
                public boolean before() {
                    System.out.println("aaa");
                    return true;
                }
    
                @Override
                public void after() {
                    System.out.println("aaa");
                }
            }).addInterceptor(new IInterceptor() {
                @Override
                public boolean before() {
                    System.out.println("bbb");
                    return false;
                }
    
                @Override
                public void after() {
                    System.out.println("bbb");
                }
            }).execute();
    
            System.out.println(result);
        }
    }
    

    执行结果:

    aaa
    bbb
    aaa
    执行失败
    

    先进入拦截器一的before,输出aaa,返回true,继续执行
    进入拦截器二的before,输出bbb,返回false.停止继续执行,返回执行失败.
    回到上一层的代码.继续执行拦截器一的after.输出aaa.然后返回.
    和预期的相符.

    总结

    拦截器模式,无论纯粹或变种,在实际开发和框架中都会时常看到,需要我们掌握.并且在合适的时机予以使用.

    参考资料

    1. https://zhuanlan.zhihu.com/p/24737592
    2. 设计模式之禅

    相关文章

      网友评论

        本文标题:责任链模式的学习与应用

        本文链接:https://www.haomeiwen.com/subject/zmicuxtx.html