责任链模式的学习与应用

定义

责任链模式(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. 设计模式之禅
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容