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; } |
具體處理者的示意性原始碼:
public class ConcreteHandler extends Handler { public void handleRequest() { if (getSuccessor() != null) { getSuccessor().handleRequest(); } if (successor != null) { successor.handleRequest(); } // Write your code here } } |
客戶端的原始碼如下:
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、擊鼓傳花系統的類圖定義。
單獨考慮擊鼓傳花系統,那麼像賈母、賈赦、賈政、賈寶玉和賈環等傳花者均應當是“具體傳花者”的物件,而不應當是單獨的類;但是責任鏈模式往往是建立在現有系統的基礎之上的,因此鏈的結構和組成不由責任鏈模式本身決定。
系統的分析
在《紅樓夢》第七十五回裡生動地描述了賈府裡的一場擊鼓傳花遊戲:“賈母坐下,左垂首賈赦,賈珍,賈璉,賈蓉,右垂首賈政,寶玉,賈環,賈蘭,團團圍坐。...賈母便命折一枝桂花來,命一媳婦在屏後擊鼓傳花。若花到誰手中,飲酒一杯...於是先從賈母起,次賈赦,一一接過。鼓聲兩轉,恰恰在賈政手中住了,只得飲了酒。”這場遊戲接著又把花傳到了寶玉和賈赦手裡,接著又傳到了在賈環手裡...
如果用一個物件系統描述賈府,那麼賈母、賈赦、賈政、賈寶玉和賈環等等就應當分別由一個個具體類代表,而這場擊鼓傳花遊戲的類圖,按照責任鏈模式,應當如下圖所示:
圖6、紅樓夢中的擊鼓傳花遊戲的示意性類圖。
換言之,在擊鼓傳花遊戲裡面,有下面的幾種角色:
- 抽象傳花者,或Handler角色、定義出參加遊戲的傳花人要遵守的規則,也就是一個處理請求的介面 和對下家的引用;
- 具體傳花者,或ConcreteHandler角色、每一個傳花者都知道下家是誰,要麼執行酒令,要麼把花 向下傳。這個角色由賈母、賈赦、賈珍、賈璉、賈蓉、賈政、寶玉、賈環、賈蘭等扮演。
- 擊鼓人,或Client角色、即行酒令的擊鼓之人。《紅樓夢》沒有給出此人的具體姓名,只是說由“一 媳婦”扮演。
圖7、賈府這次擊鼓傳花的示意性物件圖。
可以看出,擊鼓傳花遊戲滿足責任鏈模式的定義,是純的責任鏈模式的例子.
Java系統的解 下面的類圖給出了這些類的具體介面設計。讀者不難看出,DrumBeater(擊鼓者)、Player(傳花者)、JiaMu(賈母)、JiaShe(賈赦)、JiaZheng(賈政)、JiaBaoYu(寶玉)、JiaHuan(賈環)等組成這個系統。
圖8、擊鼓傳花的類圖完全符合責任鏈模式的定義。
下面是客戶端類DrumBeater的原始碼:
public class DrumBeater
{ private static Player player; static public void main(String[] args) { player = new JiaMu( new JiaShe( new JiaZheng( new JiaBaoYu(new JiaHuan(null))))); player.handle(4); } } |
程式碼清單5、DrumBeater的原始碼。
abstract class Player
{ abstract public void handle(int i); private Player successor; public Player() { successor = null; } protected void setSuccessor(Player aSuccessor) { successor = aSuccessor; } public void next(int index) { if( successor != null ) { successor.handle(index); } else { System.out.println("Program terminated."); } } } |
程式碼清單6、抽象傳花者Play類的原始碼。
抽象類Player給出了兩個方法的實現,以格式setSuccessor(),另一個是next()。前者用來設定一個傳花者物件的下家,後者用來將酒令傳給下家。Player類給出了一個抽象方法handle(),代表執行酒令。
下面的這些具體傳花者類將給出handle()方法的實現。
class JiaMu extends Player
{ public JiaMu(Player aSuccessor) { this.setSuccessor(aSuccessor); } public void handle(int i) { if( i == 1 ) { System.out.println("Jia Mu gotta drink!"); } else { System.out.println("Jia Mu passed!"); next(i); } } } |
程式碼清單7、代表賈母的JiaMu類的原始碼。
class JiaShe extends Player
{ public JiaShe(Player aSuccessor) { this.setSuccessor(aSuccessor); } public void handle(int i) { if( i == 2 ) { System.out.println("Jia She gotta drink!"); } else { System.out.println("Jia She passed!"); next(i); } } } |
程式碼清單8、代表賈赦的JiaShe類的原始碼。
class JiaZheng extends Player
{ public JiaZheng(Player aSuccessor) { this.setSuccessor(aSuccessor); } public void handle(int i) { if( i == 3 ) { System.out.println("Jia Zheng gotta drink!"); } else { System.out.println("Jia Zheng passed!"); next(i); } } } |
程式碼清單9、代表賈政的JiaZheng類的原始碼。
class JiaBaoYu extends Player { public JiaBaoYu(Player aSuccessor) { this.setSuccessor(aSuccessor); } public void handle(int i) { if( i == 4 ) { System.out.println("Jia Bao Yu gotta drink!"); } else { System.out.println("Jia Bao Yu passed!"); next(i); } } } |
程式碼清單10、代表賈寶玉的JiaBaoYu類的原始碼。
class JiaHuan extends Player
{ public JiaHuan(Player aSuccessor) { this.setSuccessor(aSuccessor); } public void handle(int i) { if( i == 5 ) { System.out.println("Jia Huan gotta drink!"); } else { System.out.println("Jia Huan passed!"); next(i); } } } |
程式碼清單11、代表賈環的JiaHuan類的原始碼。
可以看出,DrumBeater設定了責任鏈的成員和他們的順序:責任鏈由賈母開始到賈環,周而復始。JiaMu類、JiaShe類、JiaZheng類、JiaBaoYu類與JiaHuan類均是抽象傳花者Player類的子類。
本節所實現的DrumBeater類在把請求傳給賈母時,實際上指定了由4號傳花者處理酒令。雖然DrumBeater並不知道哪一個傳花者類持有號碼4,但是這個號碼在本系統一開始就寫死的。這當然並不符合擊鼓傳花遊戲的精神,因為這個遊戲實際上要求有兩個同時進行的過程:擊鼓過程和傳花過程。擊鼓應當是定時停止的,當擊鼓停止時,執行酒令者就確定了。但是本節這樣做可以使問題得到簡化並將讀者的精力放在責任鏈模式上,而不是兩個過程的處理上。
下一章會給出一個多執行緒的系統,更加逼真地模擬擊鼓傳花系統。
在什麼情況下使用責任鏈模式
在下面的情況下使用責任鏈模式:
第一、系統已經有一個由處理者物件組成的鏈。這個鏈可能由複合模式給出,
第一、當有多於一個的處理者物件會處理一個請求,而且在事先並不知道到底由哪一個處理者物件處理一個請求。這個處理者物件是動態確定的。
第二、當系統想發出一個請求給多個處理者物件中的某一個,但是不明顯指定是哪一個處理者物件會處理此請求。
第三、當處理一個請求的處理者物件集合需要動態地指定時。
使用責任鏈模式的長處和短處
責任鏈模式減低了發出命令的物件和處理命令的物件之間的耦合,它允許多與一個的處理者物件根據自己的邏輯來決定哪一個處理者最終處理這個命令。換言之,發出命令的物件只是把命令傳給鏈結構的起始者,而不需要知道到底是鏈上的哪一個節點處理了這個命令。
顯然,這意味著在處理命令上,允許系統有更多的靈活性。哪一個物件最終處理一個命令可以因為由那些物件參加責任鏈、以及這些物件在責任鏈上的位置不同而有所不同。
責任鏈模式的實現
鏈結構的由來
值得指出的是,責任鏈模式並不創建出責任鏈。責任鏈的建立必須有系統的其它部分完成。
責任鏈模式減低了請求的傳送端和接收端之間的耦合,使多個物件都有機會處理這個請求。一個鏈可以是一條線,一個樹,也可以是一個環。鏈的拓撲結構可以是單連通的或多連通的,責任鏈模式並不指定責任鏈的拓撲結構。但是責任鏈模式要求在同一個時間裡,命令只可以被傳給一個下家(或被處理掉);而不可以傳給多於一個下家。在下面的圖中,責任鏈是一個樹結構的一部分。
圖9、責任鏈是系統已有的樹結構的一部分。圖中有陰影的物件給出了一個可能的命令傳播路徑。
責任鏈的成員往往是一個更大的結構的一部分。比如在前面所討論的《紅樓夢》中擊鼓傳花的遊戲中,所有的成員都是賈府的成員。如果責任鏈的成員不存在,那麼為了使用責任鏈模式,就必須建立它們;責任鏈的具體處理者物件可以是同一個具體處理者類的例項。
在Java的1.0版的AWT事件處理模型裡,責任鏈便是視窗上的部件的容器等級結構。
在下面會談到的Internet Explorer的DHTML的DOM事件處理模型裡,責任鏈則是DOM等級結構本身。
命令的傳遞
在一個責任鏈上傳遞的可能不只有一個命令,而是數個命令。這些命令可以採取抽象化層、具體化層的多型性實現方式,見下圖,從而可以將命令物件與責任鏈上的物件之間的責任分隔開,並將命令物件與傳播命令的物件分隔開。
圖10、多個命令在責任鏈上的傳播。
當然如果責任鏈上的傳播的命令只有一個、且是固定的命令,那麼這個命令不一定要物件化。這就是本節處理擊鼓傳花遊戲裡面傳來傳去的花束的辦法。花束代表酒令,可以由一個物件代表;但是本章的處理是過程式的,用對下家物件的next()方法的呼叫達成。
物件的樹結構
在面嚮物件的技術裡,物件的樹結構是一個強有力的工具,更是模式理論的一個重要的組成部分,需要應用到符合模式、裝飾模式和迭代子模式。
《墨子.天志》說:“庶人竭力從事,未得次己而為政,有士政之,士竭力從事,未得次己而為政,有將軍、大夫政之;將軍、大夫竭力從事,未得次己而為政,有三公、諸侯政之;三公、諸侯竭力聽治,未得次己而為政,有天子政之;天子未得次己而為政,有天政之。”
“次”意為恣意。上面的話就是說,百姓有官吏管治,官吏由將軍和士大夫管治,將軍和士大夫由三公和諸侯管治,三公和諸侯由天子管治,天子由天管治。
圖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)機制。
顯然,這三級物件組成一個責任鏈,而事件便是命令或請求。當事件沿著責任鏈傳播時,責任鏈上的物件可以選擇處理或不處理此事件;不論事件在某一個等級上是否得到處理,事件都可以停止上浮或繼續上浮。這是不純的責任鏈模式
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)機制。
顯然,這三級物件組成一個責任鏈,而事件便是命令或請求。當事件沿著責任鏈傳播時,責任鏈上的物件可以選擇處理或不處理此事件;不論事件在某一個等級上是否得到處理,事件都可以停止上浮或繼續上浮。這是不純的責任鏈模式
問答題答案
第一題答案、這是一個純的責任鏈模式。
首先,在“豬”牌放出之後,每個人都只能要麼躲過“豬”牌,要麼吃住“豬”牌。“豬”牌便是責任鏈模式中的請求,四個人便是四個處理者物件,組成責任鏈。
每一個參加者的行為不僅僅取決於他手中的牌,而且取決於他是否想得“豬”牌。一個想收全紅的人,可能會權力攬“豬”牌,一個不想收全紅的人,一般不想收“豬”牌,除非他想阻止別人收“豬”牌。因為一旦有人收全紅,另外三個人就會復出較大的代價,因此阻止別人收全紅的動機,會促使一個參與者主動收“豬”牌。有的時候,放出“豬”牌的人也會想要得“豬”牌而得不到,有的時候放出“豬”牌的人想要害人但卻害了自己。
這就是說,到底是四個人中的哪一個人得到“豬”牌是完全動態決定的。
系統的UML結構圖如下:
圖18、紙牌遊戲“拱豬”的UML類圖。
由於玩牌的時候,可能有四人位置的任意調換,或者有候補者在旁等待,一旦在任的玩家被淘汰,便可上任。這樣四個人組成的牌局是動態變化的。同時因為誰會拿到“豬”牌在每一局均會不同,因此誰會放出“豬”牌也是動態的。
因此,責任鏈的組成和順序變不是一成不變的,而是動態的和變化的。
第二題答案、墨子的守城部隊的等級結構可以用下面的物件圖表示。
圖17、物件圖,顯示墨子的守城部隊。
顯然,這是一個純的責任鏈模式。任何提出申請的兵勇便是客戶端,伍長、什長、佰長、大帥和大將是責任鏈的具體處理者物件。一個申請會在鏈上傳播,直到某一級的有合適的許可權的軍官處理申請為止。每一個申請必會得到處理,批准或駁回。一個被處理過的申請會按照相反的方向傳播,直到傳回到發出申請的兵勇手中。
發出申請的士兵在發出申請時根本不知道他的申請會向上傳播多少等級。
第三題答案、這是純的責任鏈模式。
首先,酒便是請求的代表。每一個酒會的參與者都是一個請求的處理者物件,所有的參加者組成責任鏈。一個酒杯會漂過每一個參加者,代表一個請求經過每一個請求處理者物件。
每一個酒會的參加者都有可能選擇喝掉某一杯酒,或者讓酒繼續漂向下一個參加者,而且假定所有的酒最後都會被某一個參加者喝掉,因此這是純的責任鏈模式。