对象的树结构
在面向对象的技术里,对象的树结构是一个强有力的工具,更是模式理论的一个重要的组成部分,需要应用到符合模式、装饰模式和迭代子模式。
《墨子.天志》说:“庶人竭力从事,未得次己而为政,有士政之,士竭力从事,未得次己而为政,有将军、大夫政之;将军、大夫竭力从事,未得次己而为政,有三公、诸侯政之;三公、诸侯竭力听治,未得次己而为政,有天子政之;天子未得次己而为政,有天政之。”
“次”意为恣意。上面的话就是说,百姓有官吏管治,官吏由将军和士大夫管治,将军和士大夫由三公和诸侯管治,三公和诸侯由天子管治,天子由天管治。
图11、墨子论责任和责任链的传播。图中有阴影的对象给出了一个可能的责任链选择。
当一个百姓提出要求时,此要求会传达到“士”一级,再到“大夫”一级,进而传到“诸侯”一级,“天子”一级,最后到“天”一级。
DHTML中的事件处理
浏览器的DOM(Document Object Model)模型中的事件处理均采用责任链模式。本节首先考察Netscape浏览器的DHTML的事件处理,然后再研究Internet Explorer的事件模型。
Netscape的事件模型
Netscape的事件处理机制叫做“事件捕捉”(Event Capturing)。在事件捕捉机制里面,一个事件是从DOM的最高一层向下传播,也就是说,window对象是第一个接到事件的,然后是document对象,如此往下---事件的产生对象反而是最后一个接到事件的。
如果要是一个对象捕获某一个事件,只需要调用captureEvent()方法;如果要使一个对象把某一个事件向下传而不处理此事件,只需要对此对象使用releaseEvents方法即可。下面考察一个简单的事件捕获和传递的例子。
图12、一个Netscape的例子。
在这个例子里,有一个textbox和两个button,一个叫做“Capture Event”,单击后会使网页的click事件被捕捉,文字框中的计数会加一;另一个叫做“Release Event”,单击后会使网页的click事件不被捕捉。
使click事件被捕捉需要调用captureEvent()方法,而使click事件不被捕捉需要调用releaseEvent()方法。下面是具体的html和JavaScript代码。
代码清单6、JavaScript和HTML源代码。
显然,一个事件可以在几个不同的等级上得到处理,这是一个不纯的责任链模式。
Internet Explorer的事件模型
Internet Explorer处理事件的方式与Netscape既相似又不同。当一个事件发生在Internet Explorer所浏览的网页中时,Internet Explorer会使用DHTML的“Event Bubbling”即事件浮升机制处理此事件。Internet Explorer的DOM模型是html对象等级结构和事件处理机制。在DOM里面,每一个html标示都是一个DOM对象,而每一个DOM对象都可以产生事先定义好的几个事件中的一个(或几个)。这样的一个事件会首先发生在事件所属的对象上,然后向上传播,传到此对象所属的容器对象上,如此等等。因此,事件浮升机制恰恰是事件捕捉机制的相反面。
在Event Bubbling机制里面,产生事件的对象首先会收到事件。然后,事件会依照对象的等级结构向上传播。比如一个DIV里有一个Form,Form里面又有一个Button,那么当Button的onclick事件产生时,Form的onclick事件代码就会被执行。然后,事件就会传到DIV对象。如果DIV对象的onclick事件有任何代码的话,这代码就会被执行,然后事件继续沿着DOM结构上行。
如果要阻止事件继续向上传播,可以在事件链的任何一个节点上把cancelBubble性质设置成True即可。
Internet Explorer 浏览器几乎为所有的 HTML 标识符都提供了事件句柄,因此Internet Explorer不需要captureEvents()方法和releaseEvents()方法来捕获和释放事件。下面的JavaScript语句指定了document对象的onclick事件的处理方法:
document.onclick = functionName;
而下面的语句则停止了document对象对onclick事件的处理。
document.onclick = null;
因为事件处理性质被赋值null,document便没有任何的方法处理此事件。换言之,null值禁止了此对象的事件处理。这种方法可以用到任何的对象和任何的事件上面。当然这一做法不适用于Netscape。
与Netscape中一样,一个事件处理方法可以返还Boolean值。比如,单击一个超链接标记符是否造成浏览器跟进,取决于此超链接标记符的onclick事件是否返还true。
为了显示Internet Explorer中的事件浮升机制,本节特准备了下面的例子。一个Form里面有一个Button,请见下图:
图13、一个Internet Explorer的例子。
其HTML代码请见下面:
代码清单7、JavaScript和HTML源代码。
当myButton的onclick事件发生时,myButton的事件处理首先被激发,从而显示出如下的对话窗
图14、myButton对象的事件处理被激发。
然后事件会象气泡一样浮升到上一级的对象,即myForm对象上。myForm对象的事件处理给出下面的对话窗:
图15、myFormn对象的事件处理被激发。
这以后事件继续浮升到更上一级的对象,即body上。这时,document对象的事件处理被激发,并给出下面的对象窗:
图16、document对象的事件处理被激发。
这就是事件浮升(Event Bubbling)机制。
显然,这三级对象组成一个责任链,而事件便是命令或请求。当事件沿着责任链传播时,责任链上的对象可以选择处理或不处理此事件;不论事件在某一个等级上是否得到处理,事件都可以停止上浮或继续上浮。这是不纯的责任链模式。
责任链模式与其它模式的关系
责任链模式与以下的设计模式相关:
复合模式(Composite Pattern) 当责任链模式中的对象链属于一个较大的结构时,这个较大的结构可能符合复合模式。
命令模式(Command Pattern) 责任链模式使一个特定的请求接收对象对请求或命令的执行变得不确定。而命令模式使得一个特定的对象对一个命令的执行变得明显和确定。
模版方法模式(Template Method) 当组成责任链的处理者对象是按照复合模式组成一个较大的结构的责成部分的话,模版方法模式经常用来组织单个的对象的行为。
问答题
第一题、在称为“拱猪”的纸牌游戏中,四个参加者中由“猪”牌的,可以选择一个时机放出这张“猪”牌。“猪”牌放出后,四个人中的一个会不可避免地拿到这张“猪”牌。
请使用责任链模式说明这一游戏,并给出UML结构图。
第二题、《墨子.迎敌祠》里描守城军队的结构:“城上步一甲、一戟,其赞三人。五步有伍长,十步有什长,百步有佰长,旁有大帅,中有大将,皆有司吏卒长。”
一个兵勇需要上级批准以便执行一项任务,他要向伍长请求批准。伍长如果有足够的权限,便会批准或驳回请求;如果他没有足够的权限,便会向上级,即什长转达这个请求。什长便会重复同样的过程,直到大将那里。一个请求最终会被批准或驳回,然后就会象下传,直到传回到发出请求的士兵手里。
有些请求会很快返回,有些则要经过较长的过程。请求到底由谁批准,事前并不知道。请求的处理者并不是固定的,有些军官会晋升,转业,或从别的单位转过来,等等。
请使用责任链模式解释这个核准请求的结构。
(本例子受到文献[ALPERT98]里“Chain of Responsibility”一节所给出的一个例子的启发。)
第三题、王羲之在《兰亭序》中写道:“有清流激湍,映带左右,引以为流觞曲水,列坐其次。”讲的是大伙列坐水畔,随水流放下带羽毛的酒杯饮酒。远道而来的酒杯流到谁的面前,谁就取而饮之。
在这个活动中,参加者做成一排,面对着一条弯曲的小溪。侍者把酒杯盛满酒,让酒杯沿着小溪向下漂流。酒杯漂到一个参加者面前的时候,他可以选择取酒饮之,也可以选择让酒杯漂向下家。
假设每一杯酒最终都会被参加者中之一喝掉,那么这个游戏是不是纯的责任链模式?
问答题答案
第一题答案、这是一个纯的责任链模式。
首先,在“猪”牌放出之后,每个人都只能要么躲过“猪”牌,要么吃住“猪”牌。“猪”牌便是责任链模式中的请求,四个人便是四个处理者对象,组成责任链。
每一个参加者的行为不仅仅取决于他手中的牌,而且取决于他是否想得“猪”牌。一个想收全红的人,可能会权力揽“猪”牌,一个不想收全红的人,一般不想收“猪”牌,除非他想阻止别人收“猪”牌。因为一旦有人收全红,另外三个人就会复出较大的代价,因此阻止别人收全红的动机,会促使一个参与者主动收“猪”牌。有的时候,放出“猪”牌的人也会想要得“猪”牌而得不到,有的时候放出“猪”牌的人想要害人但却害了自己。
这就是说,到底是四个人中的哪一个人得到“猪”牌是完全动态决定的。
系统的UML结构图如下:
图18、纸牌游戏“拱猪”的UML类图。
由于玩牌的时候,可能有四人位置的任意调换,或者有候补者在旁等待,一旦在任的玩家被淘汰,便可上任。这样四个人组成的牌局是动态变化的。同时因为谁会拿到“猪”牌在每一局均会不同,因此谁会放出“猪”牌也是动态的。
因此,责任链的组成和顺序变不是一成不变的,而是动态的和变化的。
第二题答案、墨子的守城部队的等级结构可以用下面的对象图表示