动机:
在开发应用程序时,经常会发生由一个对象产生的事件需要另一个对象处理的情况。并且,似乎是为了使我们的工作难度更大,我们碰巧没有需要处理这个事件的对象的访问权限。在这种情况下,有两种可能的处理方式:一个初级而又懒惰的方式是把所有东西都设置为public,创建每一个对象的引用,然后继续开发;另一种专家级的方式是使用责任链设计模式。
责任链模式允许一个对象发送一个命令,而不用知道那个对象会接收它并处理它。请求会从构成链条的一个对象传递到另一个对象,链条中的每个对象可以处理这个命令,把它传递下去或两者都做。
目的:
它避免了把请求发送者和请求接收者进行绑定,给了其他对象处理请求的可能。
对象成为了联调的一部分,请求从一个对象传递到另一个对象,直到有一个对象处理它。
实现:
类图如下:
下面是在使用责任链模式的应用程序中发送一个请求是如何运转的:client把需要处理的请求发送给处理者的链条,处理者是实现了Handler接口的类。链条中的每一个处理者依次尝试处理他收到的请求。如果ConcreteHandler_i能够处理它,那么这个请求就得到了处理,如果处理不了,他就会把请求发送给ConcreteHandler_i+1,也就是链条中的下一个处理者。
代码:
需要注意的地方是,在main函数中,我们需要自己把责任链建立起来。使用责任链时,其实是使用责任链的链头即可。(截图中的代码是有问题的。细心的读者是否可以看出来。)
适用性和例子:
当写程序的时候,有这么多设计模式可供选择,究竟选择哪一个是有难度的,下面是使用责任链模式更有效率的一些场景:
不止一个对象能够处理某个命令。
事先不知道处理者是谁。
应该能够自动决定处理者。
期望请求会发送给一组对象而不用显式的指明它的接收者。
可能处理命令的对象们必须以动态的方式进行制定。
下面是一些使用责任链模式的实际场景。
例子1:
在给一个批准采购请求的系统设计软件时。在这个场景中,采购的金额可以被分为几类,每一类需要它自己的批准权限。批准权限的金额可能随时改变,要求系统要足够灵活能够应对这种情景。
上面场景的client是等待批准的另一个系统,它发生采购请求给一个批准采购的权限。根据采购的金额,这个权限也许批准请求或者把它传递给链条中的另一个权限。
例如,采购请求是给一个办公室采购新的键盘。采购的金额不算太大,所以请求从办公室领导传递给部门领导,然后传递到物资部门,就得到了处理。但是,如果整个部门的设备都需要采购,请求就会从部门领导传递到物资部门,然后到采购办公室,如果金额足够大的话,甚至会到总经理。
例子2:
在设计一个使用GUI类来处理GUI事件的软件时。当一个事件,例如按下一个键或点击鼠标,这个事件需要被传递给产生它的对象,同时也要传递给将要处理它的对象(们)。client是产生事件的对象,请求是这个事件,处理者是能够处理它的对象。所以,如果我们有处理鼠标点击事件的处理者,处理Enter按键的处理者,和处理Delete按键的处理者,那么这就是处理产生的事件的处理者链条。
特定问题和实现
责任链的传统实现只是应用的第一步。我们需要基于我们要处理的命令类型进行改进,从而使责任链的使用更加有效。
表达请求
在现实生活中,每一个处理者代表一个系统。每一个系统能够处理一种特定的请求或者能够被多个处理者处理的请求。当实现这个模式的时候,我们应该把这种情况考虑在内。在网上能够找到的责任链的传统例子中,请求经常被一个整数来代表。当然在实际生活中我们不能用基本数据类型来作为一个请求。
一个聪明的设计应该是一个灵活的。一个最好的解决方法是,创建一个接口或基类(Request)。如果我们需要增加一个新的处理者和特定请求,我们要做的仅仅是继承基类Request即可。
当然,这不是唯一的解决方法。如果每个请求需要携带大量的数据,创建这样的请求有可能会比较困难。我们可以用包含这些信息的xml对象来在系统间传递。
或者如果数据已经被保存在数据库中,我们可以只传递相关对象的id(s),每个处理者从数据库中读取数据即可。
未处理的请求
不幸的是,责任链不保证每一个命令都会被处理,使问题更加糟糕的是,因为未处理的命令在整个联调进行传递,会降低系统的效率。解决这个问题的一个方法是,在链条的结尾进行检查,这个请求是否已经被处理过至少一次。否则,我们需要实现能够处理可能出现的所有请求的处理者。
断开的链条
有时候,在实现处理方法的时候,我们可能忘记了调用下一个处理者的方法,这就会导致断开的链条。请求在断开的地方没有被传递下去,所以它还未被处理就结束了。我们可以修改一下责任链的实现方式,把处理者中的条件移除,保证把请求发送给所有处理者,并且一定会调用下一个处理者的方法。
下面的实现消除了断开链条的问题。这种实现方式,把构建链条的代码移到了基类中,把请求的处理放在子类的另一个方法中。基类中的handleRequest方法被声明为final的,它负责构建链条。每一个处理者需要实现基类中的handleRequestImpl虚方法。
上面的实现不仅消除了断开的链条的问题,并且它也提供了更高层次的灵活性。只需要通过修改handleRequest方法,我们就能修改发送给其他处理者的方式,而不用关心消息的处理。
避免垃圾请求
例如,我们能发现一个有用的改进是避免发送垃圾命令。这样的话,handleRequest方法的具体实现就会像下面的样子:
在已经存在的代码中使用责任链模式
责任链模式带给程序员的最后一个但不是最不重要的问题是这样一个事实,在不修改源代码的情况下几乎不可能把责任链模式引入到已经写好的类,甚至在代码中已经包含这个模式情况下,想要把新的操作需要加入到处理者也做不到。所以基本的思路是在一开始就决定我们是否要使用责任链模式,如果要使用,哪些方法要使用。
热点:
责任链模式的一个基本缺点是它很容易断开:如果程序员在concreteHandler中忘记调用下一个处理者的方法,那么请求在半路就被丢掉了。
责任链模式的另一个缺点是,一些请求因为具体处理者的错误实现而还得不到处理,它们的传递降低了系统的效率。这就意味着当考虑可能出现的请求时需要格外注意。