每天学习一个设计模式(十三):行为型之责任链模式

一、基本概念

责任链模式定义如下:Avoid coupling the sender of a request to its receiver by giving more than one object a chance tohandle the request.Chain the receiving objects and pass the request along the chain until an objecthandles it.(使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。)

二、通俗解释

CHAIN OF RESPONSIBLEITY责任链模式:晚上去上英语课,为了好开溜坐到了最后一排,哇,前面坐了好几个漂亮的MM哎,找张纸条,写上“Hi,可以做我的女朋友吗?如果不愿意请向前传”,纸条就一个接一个的传上去了,糟糕,传到第一排的MM把纸条传给老师了,听说是个老处女呀,快跑! 责任链模式:在责任链模式中,很多对象由每一个对象对其下家的引用而接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。客户并不知道链上的哪一个对象最终处理这个请求,系统可以在不影响客户端的情况下动态的重新组织链和分配责任。处理者有两个选择:承担责任或者把责任推给下家。一个请求可以最终不被任何接收端对象所接受。

三、责任链模式详解

1.古代妇女的枷锁——“三从四德”

中国古代对妇女制定了“三从四德”的道德规范,“三从”是指“未嫁从父、既嫁从夫、夫死从子”。也就是说,一位女性在结婚之前要听从于父亲,结婚之后要听从于丈夫,如果丈夫死了还要听从于儿子。举例来说,如果一位女性要出去逛街,在她出嫁前必须征得父亲的同意,出嫁之后必须获得丈夫的许可,那丈夫死了怎么办?那就得问问儿子是否允许自己出去逛街。估计你接下来马上要问:“要是没有儿子怎么办?”那就请示小叔子、侄子等。在父系社会中,妇女只占从属地位,现在想想中国古代的妇女还是挺悲惨的,连逛街都要多番请示。作为父亲、丈夫或儿子,只有两种选择:要不承担起责任来,允许她或不允许她逛街;要不就让她请示下一个人,这是整个社会体系的约束,应用到我们项目中就是业务规则。下面来看如何通过程序来实现“三从”,需求很简单:通过程序描述一下古代妇女的“三从”制度。好,我们先来看类图,如图所示。

类图非常简单,IHandler是三个有决策权对象的接口,IWomen是女性的代码,其实现也非常简单,IWomen如下:

public interface IWomen {
    /**
     * 获得个人状况
     *
     * @return int
     */
    public int getType();

    /**
     * 获得个人请示,你要干什么?出去逛街?约会?还是看电影?
     *
     * @return string
     */
    public String getRequest();
}

女性接口仅两个方法,一个是取得当前的个人状况getType,通过返回值决定是结婚了还是没结婚、丈夫是否在世等,另外一个方法getRequest是要请示的内容,要出去逛街还是吃饭。

public class Women implements IWomen {
    /**
     * 妇女状况,1:未出嫁,2:出嫁,3:夫死
     */
    private int type = 0;
    /**
     * 妇女的请示
     */
    private String request = "";

    /**
     * 构造函数传递过来请求
     *
     * @param type
     * @param request
     */
    public Women(int type, String request) {
        this.type = type;
        this.request = request;
    }

    @Override
    public int getType() {
        return this.type;
    }

    @Override
    public String getRequest() {
        return this.request;
    }
}

我们使用数字来代表女性的不同状态:1是未结婚;2是已经结婚的,而且丈夫健在;3是丈夫去世了。从整个设计上分析,有处理权的人(如父亲、丈夫、儿子)才是设计的核心,他们是要处理这些请求的,我们来看有处理权的人员接口IHandler。

public interface IHandler {
    /**
     * 一个女性要求逛街,你要处理这个请求
     * @param women
     */
    void handleMessage(IWomen women);
}

非常简单,有处理权的人对妇女的请求进行处理,分别有三个实现类,在女儿没有出嫁之前父亲是有决定权的。

public class Father implements IHandler {
    @Override
    public void handleMessage(IWomen women) {
        //未出嫁的女儿来请示父亲
        System.out.println("女儿的请示是:" + women.getRequest());
        System.out.println("父亲的答复是:同意");
    }
}

在女性出嫁后,丈夫有决定权。

public class Husband implements IHandler {
    @Override
    public void handleMessage(IWomen women) {
        //妻子向丈夫请示
        System.out.println("妻子的请示是:" + women.getRequest());
        System.out.println("丈夫的答复是:同意");
    }
}

在女性丧偶后,对母亲提出的请求儿子有决定权。

public class Son implements IHandler {
    @Override
    public void handleMessage(IWomen women) {
        //母亲向儿子请示
        System.out.println("母亲的请示是:" + women.getRequest());
        System.out.println("儿子的答复是:同意");
    }
}

上三个实现类非常简单,只有一个方法,处理女儿、妻子、母亲提出的请求,我们来模拟一下一个古代妇女出去逛街是如何请示的。

public class Client {
    public static void main(String[] args) {
        //随机挑选几个女性
        Random random = new Random();
        ArrayList<IWomen> arrayList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            arrayList.add(new Women(random.nextInt(3) + 1, "我要出去逛街"));
        }
        //定义三个请示对象
        IHandler father = new Father();
        IHandler husband = new Husband();
        IHandler son = new Son();
        for (IWomen women : arrayList) {
            switch (women.getType()) {
                case 1:
                    System.out.println("--------女儿向父亲请示--------");
                    father.handleMessage(women);
                    break;
                case 2:
                    System.out.println("--------妻子向丈夫请示--------");
                    husband.handleMessage(women);
                    break;
                case 3:
                    System.out.println("--------母亲向儿子请示--------");
                    son.handleMessage(women);
                    break;
                default:
                    break;
            }
        }
    }
}

首先是通过随机方法产生了5个古代妇女的对象,然后看她们是如何就逛街这件事去请示的,运行结果如下所示(由于是随机的,您看到的结果可能和这里有所不同):

--------女儿向父亲请示--------
女儿的请示是:我要出去逛街
父亲的答复是:同意
--------母亲向儿子请示--------
母亲的请示是:我要出去逛街
儿子的答复是:同意
--------女儿向父亲请示--------
女儿的请示是:我要出去逛街
父亲的答复是:同意
--------母亲向儿子请示--------
母亲的请示是:我要出去逛街
儿子的答复是:同意
--------妻子向丈夫请示--------
妻子的请示是:我要出去逛街
丈夫的答复是:同意

三从四德”的旧社会规范已经完整地表现出来了,你看谁向谁请示都定义出来了,但是你是不是发现这个程序写得有点不舒服?有点别扭?有点想重构它的感觉?那就对了!这段代码有以下几个问题:

 ●  职责界定不清晰

对女儿提出的请示,应该在父亲类中做出决定,父亲有责任、有义务处理女儿的请示,因此Father类应该是知道女儿的请求自己处理,而不是在Client类中进行组装出来,也就是说原本应该是父亲这个类做的事情抛给了其他类进行处理,不应该是这样的。

 ●  代码臃肿

我们在Client类中写了if...else的判断条件,而且能随着能处理该类型的请示人员越多,if...else的判断就越多,想想看,臃肿的条件判断还怎么有可读性?!

 ●  耦合过重

这是什么意思呢,我们要根据Women的type来决定使用IHandler的那个实现类来处理请求。有一个问题是:如果IHandler的实现类继续扩展怎么办?修改Client类?与开闭原则违背了!

●异常情况欠考虑

妻子只能向丈夫请示吗?如果妻子(比如一个现代女性穿越到古代了,不懂什么“三从四德”)向自己的父亲请示了,父亲应该做何处理?我们的程序上可没有体现出来,逻辑失败了!

既然有这么多的问题,那我们要想办法来解决这些问题,我们先来分析一下需求,女性提出一个请示,必然要获得一个答复,甭管是同意还是不同意,总之是要一个答复的,而且这个答复是唯一的,不能说是父亲作出一个决断,而丈夫也作出了一个决断,也即是请示传递出去,必然有一个唯一的处理人给出唯一的答复,OK,分析完毕,收工,重新设计,我们可以抽象成这样一个结构,女性的请求先发送到父亲类,父亲类一看是自己要处理的,就作出回应处理,如果女儿已经出嫁了,那就要把这个请求转发到女婿来处理,那女婿一旦去天国报道了,那就由儿子来处理这个请求,类似于如图所示的顺序处理图。

父亲、丈夫、儿子每个节点有两个选择:要么承担责任,做出回应;要么把请求转发到后序环节。结构分析得已经很清楚了,那我们看怎么来实现这个功能,类图重新修正,如图所示。

从类图上看,三个实现类Father、Husband、Son只要实现构造函数和父类中的抽象方法response就可以了,具体由谁处理女性提出的请求,都已经转移到了Handler抽象类中,我们来看Handler怎么实现.。

public abstract class Handler {
    public final static int REQUEST_FATHER = 1;
    public final static int REQUEST_HUSBAND = 2;
    public final static int REQUEST_SON = 3;
    /**
     * 能处理的级别
     */
    private int level = 0;
    /**
     * 责任传递,下一个人责任人是谁
     */
    private Handler nextHandler;
    /**
     * 每个类都要说明一下自己能处理哪些请求
     *
     * @param level
     */
    public Handler(int level) {
        this.level = level;
    }
    /**
     * 一个女性要求逛街,你要处理这个请求
     *
     * @param women
     */
    public final void handleMessage(IWomen women) {
        if (women.getType() == this.level) {
            this.response(women);
        } else {
            if (this.nextHandler != null) {
                //有后续环节,才把请求往后递送
                this.nextHandler.handleMessage(women);
            } else {
                System.out.println("---没地方请示了,按不同意处理---");
            }
        }
    }
    /**
     * 如果不属于你处理的请求,你应该让她找下一个环节的人,如果女儿出嫁了,
     * 还向父亲请示是否可以逛街,那父亲就应该告诉女儿,应该找丈夫请示
     * @param handler
     */
    public void setNext(Handler handler) {
        this.nextHandler = handler;
    }
    /**
     * 有请示那当然要回应
     * @param women
     */
    protected abstract void response(IWomen women);
}

方法比较长,但是还是比较简单的,读者有没有看到,其实在这里也用到模板方法模式,在模板方法中判断请求的级别和当前能够处理的级别,如果相同则调用基本方法,做出反馈;如果不相等,则传递到下一个环节,由下一环节做出回应,如果已经达到环节结尾,则直接做不同意处理。基本方法response需要各个实现类实现,每个实现类只要实现两个职责:一是定义自己能够处理的等级级别;二是对请求做出回应,我们首先来看首节点Father类。

public class Father extends Handler {
    /**
     * 父亲只处理女儿的请求
     */
    public Father() {
        super(Handler.REQUEST_FATHER);
    }

    @Override
    protected void response(IWomen women) {
        System.out.println("--------女儿向父亲请示--------");
        System.out.println(women.getRequest());
        System.out.println("父亲的答复是:同意");
    }
}

丈夫类定义自己能处理的等级为2的请示。

public class Husband extends Handler {
    /**
     * 丈夫只处理妻子的请求
     */
    public Husband() {
        super(Handler.REQUEST_HUSBAND);
    }

    @Override
    protected void response(IWomen women) {
        System.out.println("--------妻子向丈夫请示--------");
        System.out.println(women.getRequest());
        System.out.println("丈夫的答复是:同意");
    }
}

儿子类只能处理等级为3的请示。

public class Son extends Handler {
    /**
     * 儿子只处理母亲的请求
     */
    public Son() {
        super(Handler.REQUEST_SON);
    }

    @Override
    protected void response(IWomen women) {
        System.out.println("--------母亲向儿子请示--------");
        System.out.println(women.getRequest());
        System.out.println("儿子的答复是:同意");
    }
}

这三个类都很简单,构造方法是必须实现的,父类框定子类必须有一个显式构造函数,子类不实现编译不通过。通过构造方法我们设置了各个类能处理的请求类型,Father只能处理请求类型为1(也就是女儿)的请求;Husband只能处理请求类型类为2(也就是妻子)的请求,儿子只能处理请求类型为3(也就是母亲)的请求,那如果请求类型为4的该如何处理呢?在Handler中我们已经判断了,如何没有相应的处理者(也就是没有下一环节),则视为不同意。

Women类的接口没有任何变化,实现类稍微有些变化。

public class Women implements IWomen {
    /**
     * 妇女状况,1:未出嫁,2:出嫁,3:夫死
     */
    private int type = 0;
    /**
     * 妇女的请示
     */
    private String request = "";

    /**
     * 构造函数传递过来请求
     *
     * @param type
     * @param request
     */
    public Women(int type, String request) {
        this.type = type;
        switch (this.type) {
            case 1:
                this.request = "女儿的请求是:" + request;
                break;
            case 2:
                this.request = "妻子的请求是:" + request;
                break;
            case 3:
                this.request = "母亲的请求是:" + request;
                break;
            default:
                break;
        }
    }

    @Override
    public int getType() {
        return this.type;
    }

    @Override
    public String getRequest() {
        return this.request;
    }
}

我们再来看Client类是怎么描述古代这一个礼节的。

public class Client {
    public static void main(String[] args) {
        //随机挑选几个女性
        Random random = new Random();
        ArrayList<IWomen> arrayList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            arrayList.add(new Women(random.nextInt(3) + 1, "我要出去逛街"));
        }
        //定义三个请示对象
        Handler father = new Father();
        Handler husband = new Husband();
        Handler son = new Son();
        //设置请示顺序
        father.setNext(husband);
        husband.setNext(son);
        for (IWomen women : arrayList) {
            father.handleMessage(women);
        }
    }
}

在Client中设置请求的传递顺序,先向父亲请示,不是父亲应该解决的问题,则由父亲传递到丈夫类解决,若不是丈夫类解决的问题则传递到儿子类解决,最终的结果必然有一个返回,其运行结果如下所示。

--------妻子向丈夫请示--------
妻子的请求是:我要出去逛街
丈夫的答复是:同意
--------妻子向丈夫请示--------
妻子的请求是:我要出去逛街
丈夫的答复是:同意
--------妻子向丈夫请示--------
妻子的请求是:我要出去逛街
丈夫的答复是:同意
--------母亲向儿子请示--------
母亲的请求是:我要出去逛街
儿子的答复是:同意
--------女儿向父亲请示--------
女儿的请求是:我要出去逛街
父亲的答复是:同意

结果也正确,业务调用类Client也不用去做判断到底是需要谁去处理,而且Handler抽象类的子类可以继续增加下去,只需要扩展传递链而已,调用类可以不用了解变化过程,甚至是谁在处理这个请求都不用知道。在这种模式下,即使现代社会的一个小太妹穿越到古代(例如掉入时空隧道,或者时空突然扭转,甚至是突然魔法显灵),对“三从四德”没有任何了解也可以自由地应付,反正只要请示父亲就可以了,该父亲处理就父亲处理,不该父亲处理就往下传递。这就是责任链模式。

2.责任链模式的通用模版

责任链模式的重点是在“链”上,由一条链去处理相似的请求在链中决定谁来处理这个请求,并返回相应的结果,其通用类图如图所示。

责任链模式的核心在“链”上,“链”是由多个处理者ConcreteHandler组成的,我们先来看抽象Handler类。

public abstract class Handler {
    private Handler nextHandler;

    public final Response handleMessage(Request request) {
        Response response = null;
        //判断是否是自己的处理级别
        if (this.getHandlerLevel().equals(request.getRequestLevel())) {
            response = this.echo(request);
        } else {
            //不属于自己的处理级别,判断是否有下一个处理者
            if (this.nextHandler != null) {
                response = this.nextHandler.handleMessage(request);
            } else {
                //没有适当的处理者,业务自行处理
            }
        }
        return response;
    }

    /**
     * 设置下一个处理者是谁
     *
     * @param handler
     */
    public void setNext(Handler handler) {
        this.nextHandler = handler;
    }

    /**
     * 每个处理者都有一个处理级别
     *
     * @return
     */
    protected abstract Level getHandlerLevel();

    /**
     * 每个处理者都必须实现处理任务
     *
     * @param request
     * @return
     */
    protected abstract Response echo(Request request);
}

抽象的处理者实现三个职责:一是定义一个请求的处理方法handleMessage,唯一对外开放的方法;二是定义一个链的编排方法setNext,设置下一个处理者;三是定义了具体的请求者必须实现的两个方法:定义自己能够处理的级别getHandlerLevel和具体的处理任务echo。

注意 在责任链模式中一个请求发送到链中后,前一节点消费部分消息,然后交由后续节点继续处理,最终可以有处理结果也可以没有处理结果,读者可以不用理会什么纯的、不纯的责任链模式。同时,请读者注意handlerMessage方法前的final关键字。

我们定义三个具体的处理者,以便可以组成一个链。

public class ConcreteHandler1 extends Handler {
    @Override
    protected Level getHandlerLevel() {
        //完成处理逻辑
        return null;
    }

    @Override
    protected Response echo(Request request) {
        //设置自己的处理级别
        return null;
    }
}

public class ConcreteHandler2 extends Handler {
    @Override
    protected Level getHandlerLevel() {
        //完成处理逻辑
        return null;
    }

    @Override
    protected Response echo(Request request) {
        //设置自己的处理级别
        return null;
    }
}

public class ConcreteHandler3 extends Handler {
    @Override
    protected Level getHandlerLevel() {
        //完成处理逻辑
        return null;
    }

    @Override
    protected Response echo(Request request) {
        //设置自己的处理级别
        return null;
    }
}

在处理者中涉及三个类:Level类负责定义请求和处理级别,Request类负责封装请求,Response负责封装链中返回的结果,该三个类都需要根据业务产生,读者可以在实际应用中完成相关的业务填充。

public class Level {
    //定义一个请求和处理等级
}

public class Request {
    /**
     * 请求的等级
     * @return
     */
    public Level getRequestLevel(){
        return null;
    }
}

public class Response {
    //处理返回的数据
}

在场景类或高层模块中对链进行组装,并传递请求,返回结果。

public class Client {
    public static void main(String[] args) {
        //声明所有的处理节点
        Handler handler1 = new ConcreteHandler1();
        Handler handler2 = new ConcreteHandler2();
        Handler handler3 = new ConcreteHandler3();
        //设置链中的极端顺序1-->2-->3
        handler1.setNext(handler2);
        handler2.setNext(handler3);
        //提交请求,返回结果
        Response response = handler1.handleMessage(new Request());
    }
}

在实际应用中,一般会有一个封装类对责任模式进行封装,也就是替代Client类,直接返回链中的第一个处理者,具体链的设置不需要高层次模块关系,这样,更简化了高层次模块的调用,减少模块间的耦合,提高系统的灵活性。

3.责任链模式的优缺点

责任链模式的优点

责任链模式非常显著的优点是将请求和处理分开。请求者可以不用知道是谁处理的,处理者可以不用知道请求的全貌(例如在J2EE项目开发中,可以剥离出无状态Bean由责任链处理),两者解耦,提高系统的灵活性。

责任链模式的缺点

责任链有两个非常显著的缺点:

一是性能问题,每个请求都是从链头遍历到链尾,特别是在链比较长的时候,性能是一个非常大的问题。

二是调试不很方便,特别是链条比较长,环节比较多的时候,由于采用了类似递归的方式,调试的时候逻辑可能比较复杂。

责任链模式的注意事项

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

在例子和通用源码中Handler是抽象类,融合了模板方法模式,每个实现类只要实现两个方法:echo方法处理请求和getHandlerLevel获得处理级别,想想单一职责原则和迪米特法则吧,通过融合模板方法模式,各个实现类只要关注的自己业务逻辑就成了,至于说什么事要自己处理,那就让父类去决定好了,也就是说父类实现了请求传递的功能,子类实现请求的处理,符合单一职责原则,各个实现类只完成一个动作或逻辑,也就是只有一个原因引起类的改变,我建议大家在使用的时候用这种方法,好处是非常明显的了,子类的实现非常简单,责任链的建立也是非常灵活的。

责任链模式屏蔽了请求的处理过程,你发起一个请求到底是谁处理的,这个你不用关心,只要你把请求抛给责任链的第一个处理者,最终会返回一个处理结果(当然也可以不做任何处理),作为请求者可以不用知道到底是需要谁来处理的,这是责任链模式的核心,同时责任链模式也可以作为一种补救模式来使用。举个简单例子,如项目开发的时候,需求确认是这样的:一个请求(如银行客户存款的币种),一个处理者(只处理人民币),但是随着业务的发展(改革开放了嘛,还要处理美元、日元等),处理者的数量和类型都有所增加,那这时候就可以在第一个处理者后面建立一个链,也就是责任链来处理请求,如果是人民币,好,还是第一个业务逻辑来处理;如果是美元,好,传递到第二个业务逻辑来处理;日元、欧元……这些都不用在对原有的业务逻辑产生很大改变,通过扩展实现类就可以很好地解决这些需求变更的问题。


参考秦小波的《设计模式之禅(第2版)》

原文地址:https://www.cnblogs.com/aohongzhu/p/15174397.html