017.责任链模式

中国古代对妇女制定了“三从四德”的道德规范,“三从”是指“未嫁从父、既嫁从夫、夫死从子”,也就是说一个女性,在没有结婚的时候要听从于父亲,结了婚后听从于丈夫,丈夫死了还要听儿子的,那我们来看怎么把“三从”通过我们的程序来实现,需求很简单:通过程序描述一下古代妇女的“三从”制度,好我们先看类图:

代码如下:

/**
 * 古代悲哀女性的总称
 */
public interface IWomen {

    /**
     * 返回个人状况,已婚、未婚、是否丧偶
     * @return 个人状况
     */
    int getType();

    /** 获取个人请示:要干什么?逛街?看电影?
     * @return 个人请示
     */
    String getRequest();

}

public class Women implements IWomen {

    /**
     * 1: 未婚
     * 2: 已婚
     * 3: 丧偶
     */
    private int type = 0;

    /**
     * 妇女的请示
     */
    private String request = "";

    public Women(int type, String request) {
        this.type = type;
        this.request = request;
    }

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

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

/**
 * 父系社会,那就是男性有至高权利,handler控制权
 */
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 iWomen : arrayList) {
            if (iWomen.getType() == 1) {
                System.out.println("----------女儿向父亲请示----------");
                father.handleMessage(iWomen);
            } else if (iWomen.getType() == 2) {
                System.out.println("----------妻子向丈夫请示----------");
                husband.handleMessage(iWomen);
            } else if (iWomen.getType() == 3) {
                System.out.println("----------母亲向儿子请示----------");
                son.handleMessage(iWomen);
            }
        }

    }

}

我们上面的代码有以下几个问题:

  • 失去面向对象的意义。对女儿提出的请示,应该在父亲类中做出决定,父亲这个类应该是知道女儿的请求应该自己处理,而不是在 Client类中进行组装出来,也就是说原本应该是父亲这个类做的事情抛给了其他类进行处理;
  • 与迪米特法则相违背。我们在Client类中写了if else的判断条件,这个条件体内都是一个接口IHandler的三个实现类,谁能处理那个请求,怎么处理,直接在实现类中定义好不就结了吗?你的类我知道的越少越好,别让我猜测你类中的逻辑,想想看,把这段 if else移动到三个实现类中该怎么做?
  • 耦合过重。这个什么意思呢,我们要根据Womentype来决定使用IHandler的哪个实现类来处理请求,我问你,如果IHanlder的实现类继续扩展怎么办?修改Client类?与开闭原则违背喽!
  • 异常情况没有考虑。妻子只能向丈夫请示吗?如果妻子向自己的父亲请示了,父亲应该做何处理?我们的程序上可没有体现出来。

既然有这么多的问题,那我们要想办法来解决这些问题,我们可以抽象成这样一个结构,女性的请求先发送到父亲类,父亲类一看是自己要处理的,就回应处理,如果女儿已经出嫁了,那就要把这个请求转发到女性的丈夫来处理,如果女性丧偶了,那就由儿子来处理这个请求,类似于这样请求:

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

从类图上看,三个实现类FatherHusbandSon只要实现构造函数和父类中的抽象方法就可以了,具体怎么处理这些请求,都已经转移到了Hanlder抽象类中,我们来看Hanlder怎么实现:

public abstract class Handler {

    // 能处理的级别
    private int level = 0;

    // 责任传递,下一个人责任人是谁
    private Handler nextHandler;

    public Handler(int level) {
        this.level = level;
    }

    public void setNext(Handler handler) {
        this.nextHandler = handler;
    }

    public abstract void response(IWomen women);

    public final void handleMessage(IWomen women) {
        if (women.getType() == level) {
            response(women);
        } else {
            if (nextHandler != null) {
                nextHandler.handleMessage(women);
            } else {
                System.out.println("----------没有地方请示了,不做处理----------");
            }
        }
    }

}

有没有看到,其实在这里也用到模版方法模式,在模版方法中判断请求的级别和当前能够处理的级别,如果相同则调用基本方法,做出反馈;如果不相等,则传递到下一个环节,由下一环节做出回应。基本方法response要各个实现类都要实现,我们来看三个实现类:

public interface IWomen {

    /**
     * 返回个人状况,已婚、未婚、是否丧偶
     * @return 个人状况
     */
    int getType();

    /** 获取个人请示:要干什么?逛街?看电影?
     * @return 个人请示
     */
    String getRequest();

}

public class Women implements IWomen {

    /**
     * 1: 未婚
     * 2: 已婚
     * 3: 丧偶
     */
    private int type = 0;

    /**
     * 妇女的请示
     */
    private String 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: // do nothing
        }
    }

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

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

public abstract class Handler {

    // 能处理的级别
    private int level = 0;

    // 责任传递,下一个人责任人是谁
    private Handler nextHandler;

    public Handler(int level) {
        this.level = level;
    }

    public void setNext(Handler handler) {
        this.nextHandler = handler;
    }

    public abstract void response(IWomen women);

    public final void handleMessage(IWomen women) {
        if (women.getType() == level) {
            response(women);
        } else {
            if (nextHandler != null) {
                nextHandler.handleMessage(women);
            } else {
                System.out.println("----------没有地方请示了,不做处理----------");
            }
        }
    }

}

public class Father extends Handler {

    public Father() {
        super(1);
    }

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

public class Husband extends Handler {

    public Husband() {
        super(2);
    }

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

public class Son extends Handler {

    public Son() {
        super(3);
    }

    @Override
    public void response(IWomen women) {
        System.out.println("--------母亲向儿子请示-------");
        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, "我要出去逛街"));
        }

        Handler father = new Father();
        Handler husband = new Husband();
        Handler son = new Son();

        father.setNext(husband);
        husband.setNext(son);

        for (IWomen iWomen : arrayList) {
            father.handleMessage(iWomen);
        }
    }

}

通过在Client中设置请求的传递顺序,解决了请求到底谁来回应的问题。结果也正确,业务调用类Client也不用去做判断到底是需要谁去处理,而且Handler抽象类的子类以后可以继续增加下去,只是我们这个传递链增加而已,调用类可以不用了解变化过程,甚至是谁在处理这个请求都不用知道。

以上讲解的就是责任链模式,你看FatherHusbandSon 这三个类的处理女性的请求时是不是在传递呀,每个环节只有两个选项:要么承担责任做出回应,要么向下传递请求,最终会有环节做出回应,通用类图如下:

在通用类图中Handler是一个接口或者是抽象类,每个实现类都有两个方法handlerRequest()是处理请求,setNext()是设置当前环节怎么把不属于自己处理的请求扔给谁,对于这个类图我觉得需要改变,融合进来模版方法模式,类图如下:

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

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

责任链有一个缺点是大家在开发的时候要注意:调试不是很方便,特别是链条比较长,环节比较多的时候,由于采用了类似递归的方式,调试的时候逻辑可能比较复杂。

观察者模式也可以实现请求的传递,比如一个事件发生了,通知了观察者,同时观察者又作为一个被观察者,通知了另外一个观察者,这也形成了一个事件广播链,这和我们今天讲的责任链是有区别的:

  • 受众数量不同。观察者广播链式可以 1:N 的方式广播,而责任链则要求是的 1:1 的传递,必然有一个且只有一个类完成请求的处理;
  • 请求内容不同。观察者广播链中的信息可以在传播中改变,但是责任链中的请求是不可改变的;
  • 处理逻辑不通。观察者广播链主要用于触发联动动作,而责任链则是对一个类型的请求按照既定的规则进行处理。

本文原书:

《您的设计模式》 作者:CBF4LIFE

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