您的位置:首页 > 大数据 > 人工智能

第 17 章 责任链模式【Chain of Responsibility Pattern】

2014-07-21 10:33 323 查看
以下内容出自:<<24种设计模式介绍与6大设计原则>>

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



非常简单的类图,这个实现也非常简单,我们先看IWomen 接口:

package cn.mjorcen.Chain_of_Responsibility.service;

/**
* @author cbf4Life cbf4life@126.com
*  I'm glad to share my knowledge with you
*         all. 古代悲哀女性的总称
*/
public interface IWomen {
// 获得个人状况
public int getType();

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


  女性就两个参数,一个是当前的个人状况,是结婚了呢还是没结婚,丈夫有没有去世,另外一个是要
请示的内容,要出去逛街呀还是吃饭,我们看实现类:

package cn.mjorcen.Chain_of_Responsibility.service.impl;

import cn.mjorcen.Chain_of_Responsibility.service.IWomen;

/**
* @author cbf4Life cbf4life@126.com
*
*         I'm glad to share my knowledge with you all. 古代女性的总称
*/
public class Women implements IWomen {
/*
* 通过一个int类型的参数来描述妇女的个人状况 1---未出嫁 2---出嫁 3---夫死
*/
private int type = 0;
// 妇女的请示
private String request = "";

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

// 获得自己的状况
public int getType() {
return this.type;
}

// 获得妇女的请求
public String getRequest() {
return this.request;
}
}


我们使用数字来代表女性的不同状态,1 是未结婚,2 是已经结婚的,而且丈夫建在,3 是丈夫去世了的。

我们再来看有处理权的人员接口:

/**
* @author cbf4Life cbf4life@126.com
*
*         I'm glad to share my knowledge with you all.
*         父系社会,那就是男性有至高权利,handler控制权
*/
public interface IHandler {
// 一个女性(女儿,妻子或者是母亲)要求逛街,你要处理这个请求
public void HandleMessage(IWomen women);
}


父亲、丈夫、儿子都是这个IHandler 接口的实现者:

package cn.mjorcen.Chain_of_Responsibility.service.impl;

import cn.mjorcen.Chain_of_Responsibility.service.IHandler;
import cn.mjorcen.Chain_of_Responsibility.service.IWomen;

/**
* @author cbf4Life cbf4life@126.com
*
*         I'm glad to share my knowledge with you all. 父亲
*/
public class Father implements IHandler {
// 未出嫁女儿来请示父亲
public void HandleMessage(IWomen women) {
System.out.println("女儿的请示是:" + women.getRequest());
System.out.println("父亲的答复是:同意");
}
}


package cn.mjorcen.Chain_of_Responsibility.service.impl;

import cn.mjorcen.Chain_of_Responsibility.service.IHandler;
import cn.mjorcen.Chain_of_Responsibility.service.IWomen;

/**
* @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
*         all. 丈夫类
*/
public class Husband implements IHandler {
// 妻子向丈夫请示
public void HandleMessage(IWomen women) {
System.out.println("妻子的请示是:" + women.getRequest());
System.out.println("丈夫的答复是:同意");
}
}


package cn.mjorcen.Chain_of_Responsibility.service.impl;

import cn.mjorcen.Chain_of_Responsibility.service.IHandler;
import cn.mjorcen.Chain_of_Responsibility.service.IWomen;

/**
* @author cbf4Life cbf4life@126.com
*
*         I'm glad to share my knowledge with you all. 儿子类
*/
public class Son implements IHandler {
// 目前向儿子请示
public void HandleMessage(IWomen women) {
System.out.println("母亲的请示是:" + women.getRequest());
System.out.println("儿子的答复是:同意");
}
}


这三个类非常非常的简单,就一个方法,处理女儿、妻子、母亲提出的请求,再来看Client 是怎么组装的:

package cn.mjorcen.Chain_of_Responsibility.service.impl;

import java.util.ArrayList;
import java.util.Random;

import cn.mjorcen.Chain_of_Responsibility.service.IHandler;
import cn.mjorcen.Chain_of_Responsibility.service.IWomen;

/**
* @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
*         all. 我们后人来看这样的社会道德
*/
@SuppressWarnings("all")
public class Client {
public static void main(String[] args) {
// 随机挑选几个女性
Random rand = new Random();
ArrayList<IWomen> arrayList = new ArrayList();
for (int i = 0; i < 5; i++) {
arrayList.add(new Women(rand.nextInt(4), "我要出去逛街"));
}
// 定义三个请示对象
IHandler father = new Father();
IHandler husband = new Husband();
IHandler son = new Son();
for (IWomen women : arrayList) {
if (women.getType() == 1) { // 未结婚少女,请示父亲
System.out.println("\n--------女儿向父亲请示-------");
father.HandleMessage(women);
} else if (women.getType() == 2) { // 已婚少妇,请示丈夫
System.out.println("\n--------妻子向丈夫请示-------");
husband.HandleMessage(women);
} else if (women.getType() == 3) { // 母亲请示儿子
System.out.println("\n--------母亲向儿子请示-------");
son.HandleMessage(women);
} else {
// 暂时啥也不做
}
}
}
}


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

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


  “三从四德”的旧社会规范已经完整的表现出来了,你看谁向谁请示都定义出来了,但是你是不是发
现这个程序写的有点不舒服?有点别扭?有点想重构它的感觉?那就对了!这段代码有以下几个问题:
失去面向对象的意义。对女儿提出的请示,应该在父亲类中做出决定,父亲这个类应该是知道女儿的
请求应该自己处理,而不是在Client 类中进行组装出来,也就是说原本应该是父亲这个类做的事情抛给了
其他类进行处理;
  迪米特法则相违背。我们在Client 类中写了if…eles 的判断条件,你看这个条件体内都是一个接口
IHandler 的三个实现类,谁能处理那个请求,怎么处理,直接在实现类中定义好不就结了吗?你的类我知
道的越少越好,别让我猜测你类中的逻辑,想想看,把这段if…else 移动到三个实现类中该怎么做?
耦合过重。这个什么意思呢,我们要根据Women 的type 来决定使用IHandler 的那个实现类来处理请
求,我问你,如果IHanlder 的实现类继续扩展怎么办?修改Client 类?与开闭原则违背喽!
  异常情况没有考虑。妻子只能向丈夫请示吗?如果妻子向自己的父亲请示了,父亲应该做何处理?我
们的程序上可没有体现出来。
  既然有这么多的问题,那我们要想办法来解决这些问题,我们可以抽象成这样一个结构,女性的请求
先发送到父亲类,父亲类一看是自己要处理的,就回应处理,如果女儿已经出嫁了,那就要把这个请求转
发到女婿来处理,那女婿一旦去天国报道了,那就由儿子来处理这个请求,类似于这样请求:



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

先看类图:



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

我们来看Hanlder 怎么实现:

package cn.mjorcen.Chain_of_Responsibility.service.impl;

import cn.mjorcen.Chain_of_Responsibility.service.IWomen;

/**
* @author cbf4Life cbf4life@126.com
*
*         I'm glad to share my knowledge with you all.
*         父系社会,那就是男性有至高权利,handler控制权
*/
public abstract class Handler {
// 能处理的级别
private int level = 0;
// 责任传递,下一个人责任人是谁
private Handler nextHanlder;

// 每个类都要说明一下自己能处理哪些请求
public Handler(int _level) {
this.level = _level;
}

// 一个女性(女儿,妻子或者是母亲)要求逛街,你要处理这个请求
public final void HandleMessage(IWomen women) {
if (women.getType() == this.level) {
this.response(women);
} else {
if (this.nextHanlder != null) { // 有后续环节,才把请求往后递送
this.nextHanlder.HandleMessage(women);
} else { // 已经没有后续处理人了,不用处理了
System.out.println("-----------没地方请示了,不做处理!---------\n");
}
}
}

/*
* 如果你属于你处理的返回,你应该让她找下一个环节的人,比如 女儿出嫁了,还向父亲请示是否可以逛街,那父亲就应该告诉女儿,应该找丈夫请示
*/
public void setNext(Handler _handler) {
this.nextHanlder = _handler;
}

// 有请示那当然要回应
public abstract void response(IWomen women);
}


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

我们来看三个实现类:

package cn.mjorcen.Chain_of_Responsibility.service.impl;

import cn.mjorcen.Chain_of_Responsibility.service.IWomen;

/**
* @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
*         all. 父亲
*/
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("父亲的答复是:同意\n");
}
}


package cn.mjorcen.Chain_of_Responsibility.service.impl;

import cn.mjorcen.Chain_of_Responsibility.service.IWomen;

/**
* @author cbf4Life cbf4life@126.com
*         I'm glad to share my knowledge with you
*         all. 丈夫类
*/
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("丈夫的答复是:同意\n");
}
}


package cn.mjorcen.Chain_of_Responsibility.service.impl;

import cn.mjorcen.Chain_of_Responsibility.service.IWomen;

/**
* @author cbf4Life cbf4life@126.com
*
*         I'm glad to share my knowledge with you all. 儿子类
*/
public class Son extends Handler {
// 儿子只处理母亲的请求
public Son() {
super(3);
}

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


  这三个类都很简单,构造方法那是你必须实现的,父类已经定义了,子类不实现编译不通过,通过构造方法我们设置了各个类能处理的请求类型,Father 只能处理请求类型为1(也就是女儿)的请求;Husband只能处理请求类型类2(也就是妻子)的请求;儿子只能处理请求类型为3(也就是目前)的请求。

Women 类的接口没有任何变化,实现类少有变化,看代码:

package cn.mjorcen.Chain_of_Responsibility.service.impl;

import cn.mjorcen.Chain_of_Responsibility.service.IWomen;

/**
* @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
*         all. 古代女性的总称
*/
public class Women implements IWomen {
/*
* 通过一个int类型的参数来描述妇女的个人状况 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;
}
}

// 获得自己的状况
public int getType() {
return this.type;
}

// 获得妇女的请求
public String getRequest() {
return this.request;
}
}


client

package cn.mjorcen.Chain_of_Responsibility.service.impl;

import java.util.ArrayList;
import java.util.Random;

import cn.mjorcen.Chain_of_Responsibility.service.IWomen;

/**
* @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
*         all. 我们后人来看这样的社会道德
*/
@SuppressWarnings("all")
public class Client {
public static void main(String[] args) {
// 随机挑选几个女性
Random rand = new Random();
ArrayList<IWomen> arrayList = new ArrayList();
for (int i = 0; i < 5; i++) {
arrayList.add(new Women(rand.nextInt(4), "我要出去逛街"));
}
// 定义三个请示对象
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 抽象类的子类以
后可以继续增加下去,只是我们这个传递链增加而已,调用类可以不用了解变化过程,甚至是谁在处理这
个请求都不用知道。
  以上讲解的就是责任链模式,你看Father、Husband、Son 这三个类的处理女性的请求时是不是在传递
呀,每个环节只有两个选项:要么承担责任做出回应,要么向下传递请求,最终会有环节做出回应,通用

类图如下:



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



  想想单一职责法则和迪米特法则吧,通过融合模版方法模式,各个实现类只要关注的自己业务逻辑就
成了,至于说什么事要自己处理,那就让父类去决定好了,也就是说父类实现了请求传递的功能,子类实
现请求的处理,符合单一职责法则,各个类只作一个动作或逻辑,也就是只有一个原因引起类的改变,我
建议大家在使用的时候用这种方法,好处是非常明显的了,子类的实现非常简单,责任链的建立也非常的
灵活。
  这里顺便插一句话,在论坛上有网友不赞成我这种写法,说是没有抓住XX 模式的核心的,我想请问你
一下,XX 模式的核心是什么?就拿今天讲的责任链模式来说,GOF 是这样说的:

  Avoid coupling the sender of a request to its receiver by giving more than one object a chance
to handle the request. Chain the receiving objects and pass the request along the chain until
an object handles it.
  这是GOF 的原话,我想请问大侠,告诉我这句话是什么意思,如果你来给读者讲解,你想怎么讲?翻
译成中文?拿着彦X 的那本书给读者讲?照本宣科?你觉的还有人来看吗?模式是在经验中积累的,是知
识的结晶,而不是死板硬套的模子!不要因为一些写模式的书比较流行就膜拜了,自己动脑筋想想这模式
真是就只能是这样吗? 是不是有更优秀的方式来替代呢?别照抄别人的!

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

  责任链在实际的项目中使用也是比较多的,我曾经做过这样一个项目,界面上有一个用户注册功能,
注册用户分两种,一种是VIP 用户,也就是在该单位办理过业务的,一种是普通用户,一个用户的注册要
填写一堆信息,VIP 用户只比普通用户多了一个输入项:VIP 序列号,注册后还需要激活, VIP 和普通用户
的激活流程也是不同的,VIP 是自动发送邮件到用户的邮箱中就算激活了,普通用户要发送短信才能激活,
为什么呢?获得手机号码以后好发广告短信呀!这个功能项目组就采用了责任链模式,甭管从前台传递过
来的是VIP 用户信息还是普通用户信息,统一传递到一个处理入口,通过责任链来完成任务的处理,

类图如下:



  其中RegisterAction 是继承了Strust2 中的ActionSupport,实现HTTP 传递过来的对象组装,组装出
一个HashMap 对象UserInfoMap,传递给handler 的两个实现类,具体是那个实现类来处理的,就由HashMap
上的用户标识来做决定了,这个和上面我们举的例子很类似,代码大家自己实现。
  还有一个问题需要和大家说明一下,观察者模式也可以实现请求的传递,比如一个事件发生了,通知
了观察者,同时观察者又作为一个被观察者,通知了另外一个观察者,这也形成了一个事件广播链,这和
我们今天讲的责任链是有区别的:
  受众数量不同。观察者广播链式可以1:N 的方式广播,而责任链则要求是的1:1 的传递,必然有一个
且只有一个类完成请求的处理;

  请求内容不同。

观察者广播链中的信息可以在传播中改变,但是责任链中的请求是不可改变的;处理逻辑不通。

观察者广播链主要用于触发联动动作,而责任链则是对一个类型的请求按照既定的规则进行处理。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: