java 责任链设计模式

是什么?为什么使用责任链设计模式?

参考:https://zhuanlan.zhihu.com/p/24737592

一、责任链设计模式的定义及使用场景

(1)定义

责任链模式(Chain of Responsibility)使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象能够处理它。

可以这样理解:老师出了一道题,给三个学生(按照小学生 -> 中学生 -> 大学生的顺序)依次解答。小学生拿到了这道题,没有解出来,就把解答的机会让给了中学生。结果中学生还是没有解出来,机会让给了大学生…大学生相当靠谱,终于把这道题解决了!因为整个传递问题的过程是有顺序的链式,所以可以理解为责任链。

(2)例子

继续上面的例子。假设这道题目很难,只有IQ在150以上的同学才能解出来,我们要怎么模拟小学生(IQ = 0)、中学生(IQ = 100)、大学生(IQ = 200)的做题场景呢?

1.不使用责任链设计模式

TestCommon.java

很显然,运行结果为:

在编程中,这样处理业务逻辑的方法算是非常常见了,不仅直观,而且简单明了。但是,这种写法就是最优解吗?

现在老师出了这样一个问题:“老婆很漂亮是一种怎样的体验?”(装逼模式开启)

很明显,这个问题是大学生才能驾驭的…除此之外,还有更多的判断条件:要能娶老婆,必须是男同学吧?年龄至少要18岁以上吧?长得要高吧?长得要帅吧?职业不能是程序员吧?很明显,只有满足以上条件的同学,才有资格回答这个问题。

总之,加上了一大堆判断条件,我们的程序变成了这样:

CommonTest.java

运行结果为:

随着条件的增多,代码好像变得恶心了…

别忘了,现在只有三个学生(studentA、studentB、studentC)解答这个问题…如果我们从学生A一直问到学生Z,就会出现1个if和25个else if…整个代码逻辑就会变成一大坨,完全无法维护。

除此之外,中学生还分为初中生、高中生,大学生还分为本科生、专科生、博士生…如果条件变多了,同一文件中就会有一大堆代码…

还有,现在的提问顺序是从小学生开始,问到大学生为止。现在需求DESC一下,要从大学生问到小学生,修改起来也很麻烦。

总之,如果不使用责任链设计模式,会有以下缺点:

  1. 代码臃肿: 实际应用中的判定条件往往比较复杂(比如需要查询数据库,等等),这就会产生许多额外的代码。如果判断条件比较多,代码就会大量地堆积在同一个文件中。
  2. 耦合度高:如果我们想继续添加处理请求的类,那就需要添加更多 else if 的判定条件。此外,条件判定的顺序是写死的,如果想改变顺序,只能修改这个条件语句,相当不方便。

2.吃亏之后,使用责任链设计模式

责任链模式的类图非常简单,如下图:

责任链

  1. 抽象处理类: 主要包含一个指向下一处理类的成员变量 nextHandler 和一个处理请求的方法 handRequest,handRequest 方法的主要思想是,如果满足处理的条件,则有本处理类来进行处理,否则由 nextHandler 来处理。
  2. 具体处理类:具体处理类的主要是对具体的处理逻辑和处理的适用条件进行实现。

我们实际处理上面的问题(为了方便,只用IQ作为条件)。

首先,老师提出了一个问题。这个问题就是一个请求,可以写出这样一个请求类:

然后再定义一个抽象学生类,用于实现责任链:

接下来,创建具体的学生类,设置IQ即可(相应的条件):

最后进行调用:

运行结果为:

这个模式还是很好理解的!现在我们要把初中生和高中生分开,就可以这样改写:

修改一下责任链(调用方法):

如果要调换提问顺序(即处理顺序),修改一下责任链即可。

使用责任链涉及模式有如下好处:

  1. 责任链模式与 if…else 相比,耦合性要低一些,因为它将条件判定分散到各个处理类中(这句话怎么理解…条件判断语句还是要全部写在AbstractStudent类里面啊,我感觉有点问题),并且这些处理类的优先处理顺序可以随意的设定。如果想要添加新的handler类也是十分简单的,这符合开放闭合原则。
  2. 责任链模式带来了灵活性,但是在设置处理类前后关系时,一定要避免在链中出现循环引用的问题(一直引用就会形成死循环)。

实际上,如果接触过spring mvc之类的框架,就会感觉似曾相识:这不就是filter的原理吗?请求到来时,要经过我们自定义顺序的filter的处理,使用的就是责任链模式。

(似乎listener也是这个原理,我不是特别清楚)

3.适用场景

借用别人的总结,责任链模式的特点主要是:

  1. 有多个对象共同对一个任务进行处理。
  2. 这些对象使用链式存储结构,形成一个链,每个对象知道自己的下一个对象。
  3. 一个对象对任务进行处理,可以添加一些操作后将对象传递给下一个任务。也可以在此对象上结束任务的处理,并结束任务。
  4. 请求方不需要关心最终是谁来处理任务。

举个与现实对应的例子:

  1. 员工向上级申请10天假期(存在一个请求)。
  2. 组长可以批1天假,科长可以批5天假,部长可以批10天假,老板能让你永远放假(存在很多能处理这个请求的对象)。
  3. 员工把请求给到组长,组长发现自己批不了10天假,于是给自己的上级科长(知道自己的下一个对象)。
  4. 科长发现自己批不了10天假,于是告诉员工:“我处理不了,交给部长处理了”(添加一些操作后将对象传递给下一个任务)。
  5. 部长最后批准员工的假期,不需要给老板确认(结束任务)。
  6. 员工最后知道自己被批了10天假,但是不知道是谁批的,也不需要知道(请求方不需要关心最终是谁来处理任务)。

二、总结

比较巧妙的设计,记录一下。