从击鼓传花谈起
击鼓传花是一种热闹而又紧张的饮酒游戏。在酒宴上宾客依次坐定位置,由一人击鼓,击鼓的地方与传花的地方是分开的,以示公正。开始击鼓时,花束就开始依次传递,鼓声一落,假如花束在某人手中,则该人就得饮酒。
假比说,贾母、贾赦、贾政、贾宝玉和贾环是五个参加击鼓传花游戏的传花者,他们组成一个环链。击鼓者将花传给贾母,开始传花游戏。花由贾母传给贾赦,由贾赦传给贾政,由贾政传给贾宝玉,又由贾宝玉传给贾环,由贾环传回给贾母,如此往复(见下图)。当鼓声停止时,手中有花的人就得执行酒令。
图1、击鼓传花。
击鼓传花便是责任链模式的应用。在责任链模式里,很多的对象由每一个对象对其下家的引用而联接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。
责任链可能是一条直线、一个环链甚至一个树结构的一部分。
责任链模式的结构
责任链模式是一种对象的行为模式,它所涉及到的角色如下:
第一、抽象处理者(Handler)角色、定义出一个处理请求的接口;假如需要,接口可以定义出一个方法,以返回对下家的引用。下图给出了一个示意性的类图:
图2、抽象处理者角色。
在图中的积累关系给出了具体子类对下家的引用,抽象方法handleRequest()规范了子类处理请求的操作。
第二、具体处理者(ConcreteHandler)角色、处理接到请求后,可以选择将请求处理掉,或者将请 求传给下家。下图给出了一个示意性的类图。
图3、具体处理者角色。
上图中的示意性的具体处理者ConcreteHandler类只有handleRequest()一个方法。
责任链模式的静态类结构可见下图:
图4、责任链模式的类图定义。
在图中还给出了一个客户端,以便读者可以更清楚地看到责任链模式是怎样应用的。抽象处理者的示意性源代码:
public class Handler
{
public void handleRequest()
{
if (sUCcessor != null)
{
successor.handleRequest();
}
// Write your code here
}
public void setSuccessor(Handler successor)
{
this.successor = successor;
}
public Handler getSuccessor()
{
return successor;
}
PRivate Handler successor;
}
代码清单1、抽象处理者的源代码。
具体处理者的示意性源代码:
public class ConcreteHandler extends Handler
{
public void handleRequest()
{
if (getSuccessor() != null)
{
getSuccessor().handleRequest();
}
if (successor != null)
{
successor.handleRequest();
}
// Write your code here
}
}
代码清单2、具体处理者的源代码。
客户端的源代码如下:
public class Client
{
private Handler handler;
public static void main(String[] args)
{
handler = new ConcreteHandler();
//write your code here
}
}
代码清单3、客户端的源代码。
纯的与不纯的责任链模式
一个纯的责任链模式要求一个具体的处理者对象只能在两个行为中选择一个:一是承担责任,二是把责任推给下家。不答应出现某一个具体处理者对象在承担了一部分责任后又把责任向下传的情况。
在一个纯的责任链模式里面,一个请求必须被某一个处理者对象所接受;在一个不纯的责任链模式里面,一个请求可以最终不被任何接受端对象所接受。
纯的责任链模式的实际例子很难找到,一般看到的例子均是不纯的责任链模式的实现。有些人认为不纯的责任链根本不是责任链模式,这也许是有道理的;但是在实际的系统里,纯的责任链很难找到;假如坚持责任链不纯便不是责任链模式,那么责任链模式便不会有太大的意义了。
java1.0版的AWT事件处理机制
Java的1.0版中AWT库使用了责任链模式和命令模式来处理GUI的事件。由于视窗部件往往处在容器部件里面,因此当事件发生在一个部件上时,此部件的事件处理器可以处理此事件,然后决定是否将事件向上级容器部件传播;上级容器部件接到事件后可以在此处理此事件然后决定是否将事件再次向上级容器部件传播,如此往复,直到事件到达顶层部件。
事件浮升机制
比如,当一个视窗部件接到一个MOUSE_CLICKED事件时,事件首先传播到它所发生的部件上,然后向其容器部件传播。容器可以选择处理这个事件,或者再将此事件向更高一级的容器部件传播。事件如此一级级地向上传播,就像水底的气泡一点一点地冒到水面上一样,因此又叫做事件浮升(Event Bubbling)机制。下面就是一段典型的Java1.0版的AWT库里处理事件的代码:
public boolean action(Event event, Object obj)
{
if (event.target == BTnOK)
{
doOKBtnAction();
}
else if (event.target == btnExit)
{
doExitBtnAction();
}
else
{
return super.action(event, obj);
}
return true;
}
代码清单4、Java1.0版本中AWT处理事件的典型代码。
在这段代码里面,action()判定目标部件是不是btnOK或btnExit;假如是,便运行相应的方法;假如不是,便返还true。一个方法返还true便使得事件停止浮升。
AWT1.0的事件处理的模型的缺点之一
AWT1.0的事件处理的模型是基于继续的。为了使一个程序能够捕捉GUI的事件并处理此事件,必须subclass此部件并且给其子类配备事件处理器,也就是置换掉action()方法或者handleEvent()方法。这不是应当提倡的做法:在一个面向对象的系统里,经常使用的应当是委派,继续不应当是常态。
在一个复杂的GUI系统里,这样为所有有事件的部件提供子类,会导致很多的子类,这是不是很麻烦的吗?
当然,由于事件浮升机制,可以在部件的树结构的根部部件里面处理所有的事件。但是这样一来,就需要使用复杂的条件转移语句在这个根部部件里辨别事件的起源和处理方法。这种非常过程化的处理方法很难维护,并且与面向对象的设计思想相违反。
AWT1.0的事件处理的模型的缺点之二
由于每一个事件都会沿着部件树结构向上传播,因此事件浮升机制会使得事件的处理变得较慢。这也是缺点之一。
比如在有些操作系统中,鼠标每移动一个色素,都会激发一个MOUSE_MOVE事件。每一个这样的事件都会沿着部件的容器树结构向上传播,这会使得鼠标事件成灾。
AWT1.0的事件处理的模型的缺点之三
AWT1.0的事件处理的模型只适用于AWT部件类。这是此模型的另一个缺点。
责任链模式要求链上所有的对象都继续自一个共同的父类,这个类便是java.awt.Component类。
AWT1.0的事件处理的模型是不纯的责任链模式
显然,由于每一级的部件在接到事件时,都可以处理此事件;而不论此事件是否在这一级得到处理,事件都可以停止向上传播或者继续向上传播。这是典型的不纯的责任链模式。
AWT1.1以后的事件处理的模型
自从AWT1.1以后,AWT的事件处理模型于1.0相比有了很大的变化。新的事件处理模型是建立在观察者模式的基础之上的,而不再是责任链模式的基础之上的。
关于新的事件处理模型和观察者设计模式,请见“观察者模式”一节。
红楼梦中击鼓传花的故事
显然,击鼓传花符合责任链模式的定义。参加游戏的人是一个个的具体处理者对象,击鼓的人便是客户端对象。花代表酒令,是传向处理者的请求,每一个参加游戏的人在接到传来的花时,可选择的行为只有两个:一是将花向下传;一是执行酒令---喝酒。一个人不能既执行酒令,又向下家传花;当某一个人执行了酒令之后,游戏重新开始。击鼓的人并不知道最终是由哪一个做游戏的人执行酒令,当然执行酒令的人必然是做游戏的人们中的一个。
击鼓传花的类图结构如下:
图5、击鼓传花系统的类图定义。
单独考虑击鼓传花系统,那么像贾母、贾赦、贾政、贾宝玉和贾环等传花者均应当是“具体传花者”的对象,而不应当是单独的类;但是责任链模式往往是建立在现有系统的基础之上的,因此链的结构和组成不由责任链模式本身决定。
系统的分析
在《红楼梦》第七十五回里生动地描述了贾府里的一场击鼓传花游戏:“贾母坐下,左垂首贾赦,贾珍,贾琏,贾蓉,右垂首贾政,宝玉,贾环,贾兰,团团围坐。...贾母便命折一枝桂花来,命一媳妇在屏后击鼓传花。若花到谁手中,饮酒一杯...于是先从贾母起,次贾赦,一一接过。鼓声两转,恰恰在贾政手中住了,只得饮了酒。”这场游