Java模式开发之责任链模式

热闹而又紧张的饮酒游戏。在酒宴上宾客依次坐定位置,由一人击鼓,击鼓的地方与传花的地方是分开的,以示公正。开始击鼓时,花束就开始依次传递,鼓声一落,如果花束在某人手中,则该人就得饮酒。
  假比说,贾母、贾赦、贾政、贾宝玉和贾环是五个参加击鼓传花游戏的传花者,他们组成一个环链。击鼓者将花传给贾母,开始传花游戏。花由贾母传给贾赦,由贾赦传给贾政,由贾政传给贾宝玉,又由贾宝玉传给贾环,由贾环传回给贾母,如此往复(见下图)。当鼓声停止时,手中有花的人就得执行酒令。
   


  图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、击鼓传花系统的类图定义。
  单独考虑击鼓传花系统,那么像贾母、贾赦、贾政、贾宝玉和贾环等传花者均应当是“具体传花者”的对象,而不应当是单独的类;但是责任链模式往往是建立在现有系统的基础之上的,因此链的结构和组成不由责任链模式本身决定。
  系统的分析
  在《红楼梦》第七十五回里生动地描述了贾府里的一场击鼓传花游戏:“贾母坐下,左垂首贾赦,贾珍,贾琏,贾蓉,右垂首贾政,宝玉,贾环,贾兰,团团围坐。...贾母便命折一枝桂花来,命一媳妇在屏后击鼓传花。若花到谁手中,饮酒一杯...于是先从贾母起,次贾赦,一一接过。鼓声两转,恰恰在贾政手中住了,只得饮了酒。”这场游里,对象的树结构是一个强有力的工具,更是模式理论的一个重要的组成部分,需要应用到符合模式、装饰模式和迭代子模式。
  《墨子.天志》说:“庶人竭力从事,未得次己而为政,有士政之,士竭力从事,未得次己而为政,有将军、大夫政之;将军、大夫竭力从事,未得次己而为政,有三公、诸侯政之;三公、诸侯竭力听治,未得次己而为政,有天子政之;天子未得次己而为政,有天政之。”
  “次”意为恣意。上面的话就是说,百姓有官吏管治,官吏由将军和士大夫管治,将军和士大夫由三公和诸侯管治,三公和诸侯由天子管治,天子由天管治。
   


  图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类图。
  由于玩牌的时候,可能有四人位置的任意调换,或者有候补者在旁等待,一旦在任的玩家被淘汰,便可上任。这样四个人组成的牌局是动态变化的。同时因为谁会拿到“猪”牌在每一局均会不同,因此谁会放出“猪”牌也是动态的。
  因此,责任链的组成和顺序变不是一成不变的,而是动态的和变化的。
  第二题答案、墨子的守城部队的等级结构可以用下面的对象图表示

你可能感兴趣的:(java,设计模式,游戏,UML,网页游戏)