责任链模式的学习与应用

定义

责任链模式(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. 设计模式之禅

推荐阅读更多精彩内容