程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java模式開發之責任鏈模式

Java模式開發之責任鏈模式

編輯:關於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、擊鼓傳花系統的類圖定義。

單獨考慮擊鼓傳花系統,那麼像賈母、賈赦、賈政、賈寶玉和賈環等傳花者均應當是“具體傳花者”的對象,而不應當是單獨的類;但是責任鏈模式往往是建立在現有系統的基礎之上的,因此鏈的結構和組成不由責任鏈模式本身決定。

系統的分析  在《紅樓夢》第七十五回裡生動地描述了賈府裡的一場擊鼓傳花游戲:“賈母坐下,左垂首賈赦,賈珍,賈琏,賈蓉,右垂首賈政,寶玉,賈環,賈蘭,團團圍坐。...賈母便命折一枝桂花來,命一媳婦在屏後擊鼓傳花。若花到誰手中,飲酒一杯...於是先從賈母起,次賈赦,一一接過。鼓聲兩轉,恰恰在賈政手中住了,只得飲了酒。”這場游戲接著又把花傳到了寶玉和賈赦手裡,接著又傳到了在賈環手裡...

如果用一個對象系統描述賈府,那麼賈母、賈赦、賈政、賈寶玉和賈環等等就應當分別由一個個具體類代表,而這場擊鼓傳花游戲的類圖,按照責任鏈模式,應當如下圖所示:

圖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)機制。

顯然,這三級對象組成一個責任鏈,而事件便是命令或請求。當事件沿著責任鏈傳播時,責任鏈上的對象可以選擇處理或不處理此事件;不論事件在某一個等級上是否得到處理,事件都可以停止上浮或繼續上浮。這是不純的責任鏈模式。

責任鏈模式與其它模式的關系

責任鏈模式與以下的設計模式相關:

復合模式(Composite Pattern) 當責任鏈模式中的對象鏈屬於一個較大的結構時,這個較大的結構可能符合復合模式。

命令模式(Command Pattern) 責任鏈模式使一個特定的請求接收對象對請求或命令的執行變得不確定。而命令模式使得一個特定的對象對一個命令的執行變得明顯和確定。

模版方法模式(Template Method) 當組成責任鏈的處理者對象是按照復合模式組成一個較大的結構的責成部分的話,模版方法模式經常用來組織單個的對象的行為。

問答題

第一題、在稱為“拱豬”的紙牌游戲中,四個參加者中由“豬”牌的,可以選擇一個時機放出這張“豬”牌。“豬”牌放出後,四個人中的一個會不可避免地拿到這張“豬”牌。

請使用責任鏈模式說明這一游戲,並給出UML結構圖。

第二題、《墨子.迎敵祠》裡描守城軍隊的結構:“城上步一甲、一戟,其贊三人。五步有伍長,十步有什長,百步有佰長,旁有大帥,中有大將,皆有司吏卒長。”

一個兵勇需要上級批准以便執行一項任務,他要向伍長請求批准。伍長如果有足夠的權限,便會批准或駁回請求;如果他沒有足夠的權限,便會向上級,即什長轉達這個請求。什長便會重復同樣的過程,直到大將那裡。一個請求最終會被批准或駁回,然後就會象下傳,直到傳回到發出請求的士兵手裡。

有些請求會很快返回,有些則要經過較長的過程。請求到底由誰批准,事前並不知道。請求的處理者並不是固定的,有些軍官會晉升,轉業,或從別的單位轉過來,等等。

請使用責任鏈模式解釋這個核准請求的結構。

(本例子受到文獻[ALPERT98]裡“Chain of Responsibility”一節所給出的一個例子的啟發。)

第三題、王羲之在《蘭亭序》中寫道:“有清流激湍,映帶左右,引以為流觞曲水,列坐其次。”講的是大伙列坐水畔,隨水流放下帶羽毛的酒杯飲酒。遠道而來的酒杯流到誰的面前,誰就取而飲之。

在這個活動中,參加者做成一排,面對著一條彎曲的小溪。侍者把酒杯盛滿酒,讓酒杯沿著小溪向下漂流。酒杯漂到一個參加者面前的時候,他可以選擇取酒飲之,也可以選擇讓酒杯漂向下家。

假設每一杯酒最終都會被參加者中之一喝掉,那麼這個游戲是不是純的責任鏈模式?

問答題答案

第一題答案、這是一個純的責任鏈模式。

首先,在“豬”牌放出之後,每個人都只能要麼躲過“豬”牌,要麼吃住“豬”牌。“豬”牌便是責任鏈模式中的請求,四個人便是四個處理者對象,組成責任鏈。

每一個參加者的行為不僅僅取決於他手中的牌,而且取決於他是否想得“豬”牌。一個想收全紅的人,可能會權力攬“豬”牌,一個不想收全紅的人,一般不想收“豬”牌,除非他想阻止別人收“豬”牌。因為一旦有人收全紅,另外三個人就會復出較大的代價,因此阻止別人收全紅的動機,會促使一個參與者主動收“豬”牌。有的時候,放出“豬”牌的人也會想要得“豬”牌而得不到,有的時候放出“豬”牌的人想要害人但卻害了自己。

這就是說,到底是四個人中的哪一個人得到“豬”牌是完全動態決定的。

系統的UML結構圖如下:

圖18、紙牌游戲“拱豬”的UML類圖。

由於玩牌的時候,可能有四人位置的任意調換,或者有候補者在旁等待,一旦在任的玩家被淘汰,便可上任。這樣四個人組成的牌局是動態變化的。同時因為誰會拿到“豬”牌在每一局均會不同,因此誰會放出“豬”牌也是動態的。

因此,責任鏈的組成和順序變不是一成不變的,而是動態的和變化的。

第二題答案、墨子的守城部隊的等級結構可以用下面的對象圖表示。

圖17、對象圖,顯示墨子的守城部隊。

顯然,這是一個純的責任鏈模式。任何提出申請的兵勇便是客戶端,伍長、什長、佰長、大帥和大將是責任鏈的具體處理者對象。一個申請會在鏈上傳播,直到某一級的有合適的權限的軍官處理申請為止。每一個申請必會得到處理,批准或駁回。一個被處理過的申請會按照相反的方向傳播,直到傳回到發出申請的兵勇手中。

發出申請的士兵在發出申請時根本不知道他的申請會向上傳播多少等級。

第三題答案、這是純的責任鏈模式。

首先,酒便是請求的代表。每一個酒會的參與者都是一個請求的處理者對象,所有的參加者組成責任鏈。一個酒杯會漂過每一個參加者,代表一個請求經過每一個請求處理者對象。

每一個酒會的參加者都有可能選擇喝掉某一杯酒,或者讓酒繼續漂向下一個參加者,而且假定所有的酒最後都會被某一個參加者喝掉,因此這是純的責任鏈模式。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved