Jive功能需求分析類(lèi)似于一個(gè)新系統的需求分析。只有了解Jive系統實(shí)現了哪些論壇功能,才能進(jìn)一步研究和學(xué)習它是怎樣巧妙、優(yōu)雅地實(shí)現這些功能的。
論壇系統是網(wǎng)絡(luò )交流的一種主要互動(dòng)功能系統,如圖3-1所示。通過(guò)論壇系統,用戶(hù)可以共同就某個(gè)話(huà)題不斷進(jìn)行討論,通過(guò)發(fā)貼功能發(fā)布新的話(huà)題,通過(guò)回貼功能回復別人的話(huà)題。Jive論壇系統可以允許管理員動(dòng)態(tài)地創(chuàng )建新的論壇、編輯論壇的內容、設置論壇過(guò)濾信息以及管理注冊用戶(hù)等。

圖3-1 Jive用例圖
在Jive論壇系統中,用戶(hù)角色和權限是緊密聯(lián)系在一起的。主要分兩大角色:普通用戶(hù)和管理員,具體的表現形式是通過(guò)權限組合來(lái)體現的。管理方面的權限有:
· SYSTEM_ADMIN,系統管理員,可以管理整個(gè)系統。
· FORUM_ADMIN,論壇管理員,可以管理某個(gè)特定的論壇。
· USER_ADMIN和GROUP_ADMIN,用戶(hù)和組管理員,可以管理一些特定用戶(hù)和用戶(hù)組。
論壇的讀寫(xiě)權限包括:讀權限,創(chuàng )建一個(gè)新主題,創(chuàng )建一個(gè)新的帖子等。
Jive中沒(méi)有明確定義普通用戶(hù)和管理員角色,而是直接通過(guò)以上權限組合和具體用戶(hù)直接建立聯(lián)系,并將這種直接聯(lián)系保存到數據庫中。
在權限不是很復雜的情況下,這種沒(méi)有引入角色的做法比較簡(jiǎn)單直接。但由于用戶(hù)和權限直接掛鉤,而用戶(hù)和權限都可能在不斷地動(dòng)態(tài)變化,那么它們之間由于聯(lián)系太直接和緊密,對各自變化形成了限制。所以,對于復雜的權限系統,引入了基于角色的權限系統,這將在以后章節中進(jìn)一步討論。
Jive論壇業(yè)務(wù)對象主要分為Forum、ForumThread和ForumMessage,它們之間的關(guān)系如圖3-2所示。
每個(gè)論壇Forum包含一系列ForumThread(主題),而每個(gè)主題都是由很多內容帖子ForumMessage組成的,這是一個(gè)聚集關(guān)系。這3種對象中每一個(gè)對象都涉及到對象數據的創(chuàng )建、編輯、查詢(xún)和刪除,這些對象數據分別保存在數據庫中。這3個(gè)對象對于不同的角色可操作訪(fǎng)問(wèn)權限是不一樣的,只有系統管理員和論壇管理員可以對Forum相關(guān)數據實(shí)行操作,普通用戶(hù)可以創(chuàng )建或編輯ForumThread和ForumMessage。
Jive論壇為了實(shí)現不同用戶(hù)對不同基本對象的不同操作權限,通過(guò)設定一個(gè)統一的入口,在這個(gè)入口將檢查客戶(hù)端每次對數據的操作權限,如圖3-3所示。


圖3-2 基本對象關(guān)系圖 圖3-3 入口示意圖
客戶(hù)端每次對數據庫的操作,都要經(jīng)過(guò)ForumFactory入口進(jìn)入。在ForumFactory中會(huì )動(dòng)態(tài)生成一個(gè)訪(fǎng)問(wèn)控制代理ForumFactoryProxy,通過(guò)ForumFactoryProxy檢查客戶(hù)端訪(fǎng)問(wèn)方法是否符合整體權限訪(fǎng)問(wèn)控制要求。
下面將從ForumFactory作為Jive論壇系統分析入手,結合設計模式逐步分解論壇功能的具體實(shí)現。
Jive論壇系統使用大量設計模式巧妙地實(shí)現了一系列功能。因為設計模式的通用性和可理解性,將幫助更多人很快地理解 Jive論壇源碼,從而可以依據一種“協(xié)定”來(lái)動(dòng)態(tài)地擴展它。那么使用設計模式還有哪些好處?
設計模式是一套被反復使用、多數人知曉的、經(jīng)過(guò)分類(lèi)編目的、代碼設計經(jīng)驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。毫無(wú)疑問(wèn),設計模式于己于他人于系統都是多贏(yíng)的。設計模式使代碼編制真正工程化,設計模式是軟件工程的基石。
GOF(設計模式作者簡(jiǎn)稱(chēng))《設計模式》這本書(shū)第一次將設計模式提升到理論高度,并將之規范化,該書(shū)提出了23種基本設計模式。自此,在可復用面向對象軟件的發(fā)展過(guò)程中,新的大量的設計模式不斷出現。
很多人都知道Java是完全面向對象的設計和編程語(yǔ)言,但是由于接受教育以及經(jīng)驗的原因,大多數程序員或設計人員都是從傳統的過(guò)程語(yǔ)言轉變而來(lái),因此在思維習慣上要完全轉變?yōu)槊嫦驅ο蟮脑O計和開(kāi)發(fā)方式是困難的,而學(xué)習設計模式可以更好地幫助和堅固這種轉變。
凡是學(xué)習完成設計模式的人都有一種類(lèi)似重生的感覺(jué),這種重生可以從很多方面去解釋。換一種新的角度來(lái)看待和解決問(wèn)題應該是一種比較貼切的解釋?zhuān)@種新的思維角度培養屬于基礎培訓,因此,設計模式是學(xué)習Java的必讀基礎課程之一。
由于設計模式概念比較抽象,對于初學(xué)者學(xué)習有一定的難度,因此結合Jive論壇系統學(xué)習設計模式將是一種很好的選擇。
掌握了設計模式,將會(huì )幫助程序員或設計人員以更加可重用性、可伸縮性的眼光來(lái)開(kāi)發(fā)應用系統,甚至開(kāi)發(fā)通用的框架系統??蚣芟到y是構成一類(lèi)特定軟件可復用設計的一組相互協(xié)作的類(lèi),主要是對應用系統中反復重用部分的提煉,類(lèi)似一種模板,這是一種結構性的模板。
框架通常定義了應用體系的整體結構、類(lèi)和對象的關(guān)系等設計參數,以便于具體應用實(shí)現者能集中精力于應用本身的特定細節??蚣軓娬{設計復用,而設計模式最小的可重用單位,因此框架不可避免地會(huì )反復使用到設計模式。關(guān)于通用框架系統的設計開(kāi)發(fā)將在以后章節中討論。
其實(shí)Jive論壇本身也形成了一個(gè)基于Web結構的通用框架系統,因為它很多設計思想是可以重用的,例如設定一個(gè)總體入口,通過(guò)入口檢查用戶(hù)的訪(fǎng)問(wèn)控制權限,當然還有其他各方面的功能實(shí)現方式都是值得在其他系統中借鑒的,也正因為它以模式的形式表現出來(lái),這種可重用性和可借鑒性就更強。
工廠(chǎng)模式是GOF設計模式的主要常用模式,它主要是為創(chuàng )建對象提供了一種接口,工廠(chǎng)模式主要是封裝了創(chuàng )建對象的細節過(guò)程,從而使得外界調用一個(gè)對象時(shí),根本無(wú)需關(guān)心這個(gè)對象是如何產(chǎn)生的。
在GOF設計模式中,工廠(chǎng)模式分為工廠(chǎng)方法模式和抽象工廠(chǎng)模式。兩者主要區別是,工廠(chǎng)方法是創(chuàng )建一種產(chǎn)品接口下的產(chǎn)品對象,而抽象工廠(chǎng)模式是創(chuàng )建多種產(chǎn)品接口下的產(chǎn)品對象,非常類(lèi)似Builder生成器模式。在平時(shí)實(shí)踐中,使用較多的基本是工廠(chǎng)方法模式。
以類(lèi)SampleOne為例,要創(chuàng )建SampleOne的對象實(shí)例:
SampleOne sampleOne = new SampleOne();
如果Sample類(lèi)有幾個(gè)相近的類(lèi):SampleTwo或SampleThree,那么創(chuàng )建它們的實(shí)例分別是:
SampleTwo sampleTwo = new SampleTwo();
SampleThree sampleThree = new SampleThree();
其實(shí)這3個(gè)類(lèi)都有一些共同的特征,如網(wǎng)上商店中銷(xiāo)售書(shū)籍、玩具或者化妝品。雖然它們是不同的具體產(chǎn)品,但是它們有一個(gè)共同特征,可以抽象為“商品”。日常生活中很多東西都可以這樣高度抽象成一種接口形式。上面這3個(gè)類(lèi)如果可以抽象為一個(gè)統一接口SampleIF,那么上面語(yǔ)句就可以成為:
SampleIF sampleOne = new SampleOne();
SampleIF sampleTwo = new SampleTwo();
SampleIF sampleThree = new SampleThree();
在實(shí)際情況中,有時(shí)并不需要同時(shí)生成3種對象,而是根據情況在3者之中選一個(gè)。在這種情況下,需要使用工廠(chǎng)方法來(lái)完成了,創(chuàng )建一個(gè)叫SampleFactory的抽象類(lèi):
public class SampleFactory{
public abstract SampleIFcreator();
}
在這個(gè)抽象工廠(chǎng)類(lèi)中有一個(gè)抽象方法creator,但是沒(méi)有具體實(shí)現,而是延遲到它的子類(lèi)中實(shí)現,創(chuàng )建子類(lèi)SampleFactoryImp:
public class SampleFactoryImpextends SampleFactory{
public SampleIF creator(){
//根據其他因素綜合判斷返回具體產(chǎn)品
//假設應該返回SampleOne對象
returnnew SampleOne();
}
}
在SampleFactoryImp中根據具體情況來(lái)選擇返回SampleOne、SampleTwo或SampleThree。所謂具體情況有很多種:上下文其他過(guò)程計算結果;直接根據配置文件中配置。
上述工廠(chǎng)方法模式中涉及到一個(gè)抽象產(chǎn)品接口Sample,如果還有其他完全不同的產(chǎn)品接口,如Product等,一個(gè)子類(lèi)SampleFactoryImp只能實(shí)現一套系列產(chǎn)品方案的生產(chǎn),如果還需要另外一套系統產(chǎn)品方案,就可能需要另外一個(gè)子類(lèi)SampleFactoryImpTwo來(lái)實(shí)現。這樣,多個(gè)產(chǎn)品系列、多個(gè)工廠(chǎng)方法就形成了抽象工廠(chǎng)模式。
前面已經(jīng)討論在Jive中設置了論壇統一入口,這個(gè)統一入口就是ForumFactory,以下是ForumFactory的主要代碼:
public abstract class ForumFactory{
privatestatic Object initLock = new Object();
privatestatic String className = " com.Yasna.forum.database.DbForumFactory";
privatestatic ForumFactory factory = null;
publicstatic ForumFactory getInstance(Authorization authorization){
if(authorization == null) {
returnnull;
}
//以下使用了Singleton 單態(tài)模式,將在2.3節討論
if(factory == null) {
synchronized(initLock){
if(factory == null) {
...//從配置文件中獲得當前className
try{
//動(dòng)態(tài)裝載類(lèi)
Classc = Class.forName(className);
factory= (ForumFactory)c.newInstance();
}
catch(Exception e) {
returnnull;
}
}
}
}
//返回 proxy.用來(lái)限制授權對forum的訪(fǎng)問(wèn)
returnnew ForumFactoryProxy(authorization, factory,factory.getPermissions(authorization));
}
//創(chuàng )鍵產(chǎn)品接口Forum的具體對象實(shí)例
publicabstract Forum createForum(String name, String description)
throwsUnauthorizedException, ForumAlreadyExistsException;
//創(chuàng )鍵產(chǎn)品接口ForumThread的具體對象實(shí)例
public abstract ForumThread createThread(ForumMessagerootMessage)
throws UnauthorizedException;
//創(chuàng )鍵產(chǎn)品接口ForumMessage的具體對象實(shí)例
public abstractForumMessage createMessage();
....
}
ForumFactory中提供了很多抽象方法如createForum、createThread和createMessage()等,它們是創(chuàng )建各自產(chǎn)品接口下的具體對象,這3個(gè)接口就是前面分析的基本業(yè)務(wù)對象Forum、ForumThread和ForumMessage,這些創(chuàng )建方法在ForumFactory中卻不立即執行,而是推遲到ForumFactory子類(lèi)中實(shí)現。
ForumFactory的子類(lèi)實(shí)現是com.Yasna.forum.database.DbForumFactory,這是一種數據庫實(shí)現方式。即在DbForumFactory中分別實(shí)現了在數據庫中createForum、createThread和createMessage()等3種方法,當然也提供了動(dòng)態(tài)擴展到另外一套系列產(chǎn)品的生產(chǎn)方案的可能。如果使用XML來(lái)實(shí)現,那么可以編制一個(gè)XmlForumFactory的具體工廠(chǎng)子類(lèi)來(lái)分別實(shí)現3種創(chuàng )建方法。
因此,Jive論壇在統一入口處使用了抽象工廠(chǎng)模式來(lái)動(dòng)態(tài)地創(chuàng )建論壇中所需要的各種產(chǎn)品,如圖3-4所示。

圖3-4 ForumFactory抽象工廠(chǎng)模式圖
圖3-4中,XmlForumFactory和DbForumFactory作為抽象工廠(chǎng)ForumFactory的兩個(gè)具體實(shí)現,而Forum、ForumThread和ForumMessage分別作為3個(gè)系列抽象產(chǎn)品接口,依靠不同的工廠(chǎng)實(shí)現方式,會(huì )產(chǎn)生不同的產(chǎn)品對象。
從抽象工廠(chǎng)模式去理解Jive論壇統一入口處,可以一步到位掌握了幾個(gè)類(lèi)之間的大概關(guān)系。因為使用了抽象工廠(chǎng)模式這種通用的設計模式,可以方便源碼閱讀者快速地掌握整個(gè)系統的結構和來(lái)龍去脈,圖3-4這張圖已經(jīng)初步展示了Jive的主要框架結構。
細心的讀者也許會(huì )發(fā)現,在上面ForumFactory有一個(gè)getInstance比較令人費解,這將在2.3節進(jìn)行討論。
在上面ForumFactory的getInstance方法使用單態(tài)(SingleTon)模式。單態(tài)模式是保證一個(gè)類(lèi)有且僅有一個(gè)對象實(shí)例,并提供一個(gè)訪(fǎng)問(wèn)它的全局訪(fǎng)問(wèn)點(diǎn)。
前面曾提到ForumFactory是Jive提供客戶(hù)端訪(fǎng)問(wèn)數據庫系統的統一入口。為了保證所有的客戶(hù)端請求都要經(jīng)過(guò)這個(gè)ForumFactory,如果不使用單態(tài)模式,客戶(hù)端下列調用語(yǔ)句表示生成了ForumFactory實(shí)例:
ForumFactory factory = new DbForumFactory();
客戶(hù)端每發(fā)生一次請求都調用這條語(yǔ)句,這就會(huì )發(fā)生每次都生成不同factory對象實(shí)例,這顯然不符合設計要求,因此必須使用單態(tài)模式。
一般在Java實(shí)現單態(tài)模式有幾種選擇,最常用而且安全的用法如下:
public class Singleton {
privateSingleton(){}
//在自己內部定義自己一個(gè)實(shí)例,是不是很奇怪
//注意這是private,只供內部調用
privatestatic Singleton instance = new Singleton();
//這里提供了一個(gè)供外部訪(fǎng)問(wèn)本class的靜態(tài)方法,可以直接訪(fǎng)問(wèn)
publicstatic Singleton getInstance() {
returninstance;
}
}
單態(tài)模式一共使用了兩條語(yǔ)句實(shí)現:第一條直接生成自己的對象,第二條提供一個(gè)方法供外部調用這個(gè)對象,同時(shí)最好將構造函數設置為private,以防止其他程序員直接使用new Singleton生成實(shí)例。
還有一種Java單態(tài)模式實(shí)現:
public class Singleton {
privateSingleton(){}
privatestatic Singleton instance = null;
publicstatic synchronized Singleton getInstance() {
if(instance==null)
instance=new Singleton()
returninstance;
}
}
在上面代碼中,使用了判斷語(yǔ)句。如果instance為空,再進(jìn)行實(shí)例化,這成為lazy initialization。注意getInstance()方法的synchronized,這個(gè)synchronized很重要。如果沒(méi)有synchronized,那么使用getInstance()在第一次被訪(fǎng)問(wèn)時(shí)有可能得到多個(gè)Singleton實(shí)例。
關(guān)于lazyinitialization的Singleton有很多涉及double-checked locking (DCL)的討論,有興趣者可以進(jìn)一步研究。一般認為第一種形式要更加安全些;但是后者可以用在類(lèi)初始化時(shí)需要參數輸入的情況下。
在Jive的ForumFactory中采取了后者lazy initialization形式,這是為了能夠動(dòng)態(tài)配置指定ForumFactory的具體子類(lèi)。在getInstance中,從配置文件中獲得當前工廠(chǎng)的具體實(shí)現,如果需要啟動(dòng)XmlForumFactory,就不必修改ForumFactory代碼,直接在配置文件中指定className的名字為XmlForumFactory。這樣通過(guò)下列動(dòng)態(tài)裝載機制生成ForumFactory具體對象:
Class c = Class.forName(className);
factory = (ForumFactory)c.newInstance();
這是利用Java的反射機制,可以通過(guò)動(dòng)態(tài)指定className的數值而達到生成對象的方式。
使用單態(tài)模式的目標是為了控制對象的創(chuàng )建,單態(tài)模式經(jīng)常使用在控制資源的訪(fǎng)問(wèn)上。例如數據庫連接或Socket連接等。單態(tài)模式可以控制在某個(gè)時(shí)刻只有一個(gè)線(xiàn)程訪(fǎng)問(wèn)資源。由于Java中沒(méi)有全局變量的概念,因此使用單態(tài)模式有時(shí)可以起到這種作用,當然需要注意是在一個(gè)JVM中。
仔細研究會(huì )發(fā)現,在ForumFactory的getInstance方法中最后的返回值有些奇怪。按照單態(tài)模式的概念應該直接返回factory這個(gè)對象實(shí)例,但是卻返回了ForumFactoryProxy的一個(gè)實(shí)例,這實(shí)際上改變了單態(tài)模式的初衷。這樣客戶(hù)端每次通過(guò)調用ForumFactory的getInstance返回的就不是ForumFactory的惟一實(shí)例,而是新的對象。之所以這樣做是為了訪(fǎng)問(wèn)權限的控制,姑且不論這樣做的優(yōu)劣,先看看什么是代理模式。
代理模式是屬于設計模式結構型模式中一種,它是實(shí)際訪(fǎng)問(wèn)對象的代理對象,或者影子對象,主要達到控制實(shí)際對象的訪(fǎng)問(wèn)。這種控制的目的很多,例如提高性能等。即遠程代理模式,這種模式將在以后章節討論。
其中一個(gè)主要的控制目的是控制客戶(hù)端對實(shí)際對象的訪(fǎng)問(wèn)權限。在Jive系統中,因為有角色權限的分別,對于Forum、ForumThread和FroumMessage的訪(fǎng)問(wèn)操作必須經(jīng)過(guò)權限機制驗證后才能進(jìn)行。
以ForumFactoryProxy中的createForum方法為例,其實(shí)ForumFactoryProxy也是FroumFactory的一種工廠(chǎng)實(shí)現,它的createForum具體實(shí)現如下:
public Forum createForum(Stringname, String description)
throwsUnauthorizedException, ForumAlreadyExistsException
{
if(permissions.get(ForumPermissions.SYSTEM_ADMIN)) {
ForumnewForum = factory.createForum(name, description);
returnnew ForumProxy(newForum, authorization, permissions);
}
else{
thrownew UnauthorizedException();
}
}
在這個(gè)方法中進(jìn)行了權限驗證,判斷是否屬于系統管理員。如果是,將直接從DbForumFactory對象factory的方法createForum中獲得一個(gè)新的Forum對象,然后再返回Forum的子類(lèi)代理對象ForumProxy。因為在Forum中也還有很多屬性和操作方法,這些也需要進(jìn)行權限驗證。ForumProxy和ForumFactoryProxy起到類(lèi)似的作用。
Jive中有下列幾個(gè)代理類(lèi):
· ForumFactoryProxy:客戶(hù)端和DbForumFactory之間的代理??蛻?hù)端訪(fǎng)問(wèn)DbForumFactory的任何方法都要先經(jīng)過(guò)ForumFactoryProxy相應方法代理一次。以下意思相同。
· ForumProxy:客戶(hù)端和DbForum之間的代理,研究Forum對象的每個(gè)方法,必須先看ForumProxy對象的方法。
· ForumMessageProxy:客戶(hù)端和DbForumMessage之間的代理。
· ForumThreadProxy:客戶(hù)端和DbForumThread之間的代理。
User和Group也有相應的代理類(lèi)。
由以上分析看出,每個(gè)數據對象都有一個(gè)代理。如果系統中數據對象非常多,依據這種一對一的代理關(guān)系,會(huì )有很多代理類(lèi),將使系統變得不是非常干凈,因此可以使用動(dòng)態(tài)代理來(lái)代替這所有的代理類(lèi),具體實(shí)現將在以后章節討論。
迭代(Iterator)模式是提供一種順序訪(fǎng)問(wèn)某個(gè)集合各個(gè)元素的方法,確保不暴露該集合的內部表現。迭代模式應用于對大量數據的訪(fǎng)問(wèn),Java Collection API中Iterator就是迭代模式的一種實(shí)現。
在前面章節已經(jīng)討論過(guò),用戶(hù)查詢(xún)大量數據,從數據庫不應該直接返回ResultSet,應該是Collection。但是有一個(gè)問(wèn)題,如果這個(gè)數據很大,需要分頁(yè)面顯示。如果一下子將所有頁(yè)面要顯示的數據都查詢(xún)出來(lái)放在Collection,會(huì )影響性能。而使用迭代模式則不必將全部集合都展現出來(lái),只有遍歷到某個(gè)元素時(shí)才會(huì )查詢(xún)數據庫獲得這個(gè)元素的數據。
以論壇中顯示帖子主題為例,在一個(gè)頁(yè)面中不可能顯示所有主題,只有分頁(yè)面顯示,如圖3-5所示。
圖3-5中一共分15頁(yè)來(lái)顯示所有論壇帖子,可以從顯示Forum.jsp中發(fā)現下列語(yǔ)句可以完成上述結果:
ResultFilterfilter = new ResultFilter(); //設置結果過(guò)濾器
filter.setStartIndex(start); //設置開(kāi)始點(diǎn)
filter.setNumResults(range); //設置范圍
ForumThreadIteratorthreads = forum.threads(filter); //獲得迭代器
while(threads.hasNext){
//逐個(gè)顯示threads中帖子主題,輸出圖3-5中的每一行
}

圖3-5 分頁(yè)顯示所有帖子
上述代碼中主要是從Forum的threads方法獲得迭代器ForumThreadIterator的實(shí)例,依據前面代理模式中分析、研究Forum對象的方法,首先是看ForumProxy中對應方法,然后再看DbForum中對應方法的具體實(shí)現。在ForumProxy中,threads方法如下:
public ForumThreadIterator threads(ResultFilterresultFilter) {
ForumThreadIteratoriterator = forum.threads(resultFilter);
returnnew ForumThreadIteratorProxy(iterator, authorization, permissions);
}
首先是調用了DbForum中具體的threads方法,再追蹤到DbForum中看看,它的threads方法代碼如下:
public ForumThreadIterator threads(ResultFilterresultFilter) {
//按resultFilter設置范圍要求獲得SQL查詢(xún)語(yǔ)句
String query = getThreadListSQL(resultFilter,false);
//獲得resultFilter設置范圍內的所有ThreadID集合
long [] threadBlock= getThreadBlock(query.toString(), resultFilter.getStartIndex());
//以下是計算查詢(xún)區域的開(kāi)始點(diǎn)和終點(diǎn)
int startIndex = resultFilter.getStartIndex();
int endIndex;
// If number of resultsis set to inifinite, set endIndex to the total
// number of threads inthe forum.
if (resultFilter.getNumResults()== ResultFilter.NULL_INT) {
endIndex= (int)getThreadCount(resultFilter);
}else {
endIndex= resultFilter.getNumResults() + startIndex;
}
return new ForumThreadBlockIterator(threadBlock,query.toString(),
startIndex,endIndex, this.id, factory);
}
ResultFilter是一個(gè)查詢(xún)結果類(lèi),可以對論壇主題Thread和帖子內容Message進(jìn)行過(guò)濾或排序,這樣就可以根據用戶(hù)要求定制特殊的查詢(xún)范圍。如查詢(xún)某個(gè)用戶(hù)去年在這個(gè)論壇發(fā)表的所有帖子,那只要創(chuàng )建一個(gè)ResultFilter對象就可以代表這個(gè)查詢(xún)要求。
在上面threads方法代碼中,第一步是先定制出相應的動(dòng)態(tài)SQL查詢(xún)語(yǔ)句,然后使用這個(gè)查詢(xún)語(yǔ)句查詢(xún)數據庫,獲得查詢(xún)范圍內所有的ForumThread的ID集合,然后在這個(gè)ID集合中獲得當前頁(yè)面的ID子集合,這是非常關(guān)鍵的一步。
在這關(guān)鍵的一步中,有兩個(gè)重要的方法getThreadListSQL和getThreadBlock:
· GetThreadListSQL:獲得SQL查詢(xún)語(yǔ)句query的值,這個(gè)方法Jive實(shí)現起來(lái)顯得非常地瑣碎。
· GetThreadBlock:獲得當前頁(yè)面的ID子集合,那么如何確定ID子集合的開(kāi)始位置呢?查看getThreadBlock方法代碼,可以發(fā)現,它是使用最普遍的ResultSet next()方法來(lái)逐個(gè)跳躍到開(kāi)始位置。
上面代碼的Threads方法中最后返回的是ForumThreadBlockIterator,它是抽象類(lèi)ForumThreadIterator的子類(lèi),而ForumThreadIterator繼承了Collection的Iterator,以此聲明自己是一個(gè)迭代器,ForumMessageBlockIterator實(shí)現的具體方法如下:
public boolean hasNext(); //判斷是否有下一個(gè)元素
public boolean hasPrevious() //判斷是否有前一個(gè)元素
public Object next() throws java.util.NoSuchElementException //獲得下一個(gè)元素實(shí)例
ForumThreadBlockIterator中的Block是“頁(yè)”的意思,它的一個(gè)主要類(lèi)變量threadBlock包含的是一個(gè)頁(yè)面中所有ForumThread的ID,next()方法實(shí)際是對threadBlock中ForumThread進(jìn)行遍歷,如果這個(gè)頁(yè)面全部遍歷完成,將再獲取下一頁(yè)(Block)數據。
在ForumThreadBlockIterator重要方法getElement中實(shí)現了兩個(gè)功能:
· 如果當前遍歷指針超過(guò)當前頁(yè)面,將使用getThreadBlock獲得下一個(gè)頁(yè)面的ID子集合;
· 如果當前遍歷指針在當前頁(yè)面之內,根據ID獲得完整的數據對象,實(shí)現輸出;
ForumThreadBlockIterator的getElement方法代碼如下:
private Object getElement(intindex) {
if (index < 0){ return null; }
// 檢查所要獲得的 element 是否在本查詢(xún)范圍內(當前頁(yè)面內)
if (index < blockStart||
index >= blockStart + DbForum.THREAD_BLOCK_SIZE){
try{
//從緩沖中獲得Forum實(shí)例
DbForumforum = factory.cacheManager.forumCache.get(forumID);
//獲得下一頁(yè)的內容
this.threadBlock= forum.getThreadBlock(query, index);
this.blockID= index / DbForum.THREAD_BLOCK_SIZE;
this.blockStart= blockID * DbForum.THREAD_BLOCK_SIZE;
}catch (ForumNotFoundException fnfe) {
returnnull;
}
}
Objectelement = null;
// 計算這個(gè)元素在當前查詢(xún)范圍內的相對位置
int relativeIndex= index % DbForum.THREAD_BLOCK_SIZE;
// Makesure index isn't too large
if (relativeIndex < threadBlock.length){
try{
// 從緩沖中獲得實(shí)際thread 對象
element= factory.cacheManager.threadCache.get(
threadBlock[relativeIndex]);
}catch (ForumThreadNotFoundException tnfe) { }
}
returnelement;
}
ForumThreadBlockIterator是真正實(shí)現分頁(yè)查詢(xún)的核心功能,ForumThreadBlockIterator對象返回到客戶(hù)端的過(guò)程中,遭遇ForumThreadIteratorProxy的截獲,可以回頭看看ForumProxy中的threads方法,它最終返回給調用客戶(hù)端Forum.jsp的是ForumThreadIteratorProxy實(shí)例。
ForumThreadIteratorProxy也是迭代器ForumThreadIterator的一個(gè)子類(lèi),它的一個(gè)具體方法中:
public Object next() {
return new ForumThreadProxy((ForumThread)iterator.next(),authorization,
permissions);
}
這一句是返回一個(gè)ForumThreadProxy實(shí)例,返回就是一個(gè)ForumThread實(shí)例的代理。這里,Jive使用代理模式實(shí)現訪(fǎng)問(wèn)控制實(shí)現得不是很巧妙,似乎有代理到處“飛”的感覺(jué),這是可以對之進(jìn)行改造的。
從以上可以看出,Jive在輸出如圖3-5所示的多頁(yè)查詢(xún)結果時(shí),采取了下列步驟:
(1)先查詢(xún)出符合查詢(xún)條件的所有對象元素的ID集合,注意不是所有對象元素,只是其ID的集合,這樣節約了大量?jì)却妗?/span>
(2)每個(gè)頁(yè)面視為一個(gè)Block,每當進(jìn)入下一頁(yè)時(shí),獲得下一個(gè)頁(yè)面的所有對象的ID集合。
(3)輸出當前頁(yè)面的所有對象時(shí),首先從緩沖中獲取,如果緩沖中沒(méi)有,再根據ID從數據庫中獲取完整的對象數據。
上述實(shí)現方法完全基于即查即顯,相比于一般批量查詢(xún)做法:一次性獲得所有數據,然后遍歷數據結果集ResultSet,Jive這種批量查詢(xún)方式是一種比較理想的選擇。
以上是ForumThread的批量顯示,有關(guān)帖子內容ForumMessage也是采取類(lèi)似做法。在每個(gè)ForumThread中可能有很多帖子內容(ForumMessage對象集合),也不能在一個(gè)頁(yè)面中全部顯示,所以也是使用迭代模式來(lái)實(shí)現的。顯示一個(gè)Forum主題下所有帖子內容的功能由ForumThread的messages()方法完成,檢查它的代理類(lèi)FroumThreadProxy如何具體完成:
public Iterator messages(ResultFilterresultFilter) {
Iterator iterator= thread.messages(resultFilter);
return new IteratorProxy(JiveGlobals.MESSAGE,iterator, authorization, permissions);
}
實(shí)現的原理基本相同,返回的都是一個(gè)Iterator代理類(lèi),在這些代理類(lèi)中都是進(jìn)行用戶(hù)權限檢驗的。
Jive中也有關(guān)于一次性獲得所有數據,然后遍歷ResultSet的做法。這種做法主要適合一次性查詢(xún)數據庫的所有數據,例如查詢(xún)當前所有論壇Forum,首先實(shí)現SQL語(yǔ)句:
SELECT forumID FROM jiveForum
獲得所有Forum的forumID,這段代碼位于DbForumFactory.java的forums方法中,如下:
public Iterator forums(){
if (forums ==null) {
LongListforumList = new LongList();
Connectioncon = null;
PreparedStatementpstmt = null;
try{
con= ConnectionManager.getConnection();
//GET_FORUMS值是SELECTforumID FROM jiveForum
pstmt= con.prepareStatement(GET_FORUMS);
ResultSetrs = pstmt.executeQuery();
while(rs.next()) {
forumList.add(rs.getLong(1)); //將所有查詢(xún)ID結果放入forumList中
}
}catch(SQLException sqle) {
sqle.printStackTrace();
}finally {
…
}
return new DatabaseObjectIterator(JiveGlobals.FORUM,forums, this);
}
forums方法是返回一個(gè)DatabaseObjectIterator,這個(gè)DatabaseObjectIterator也是一個(gè)迭代器,但是實(shí)現原理要比ForumThreadBlockIterator簡(jiǎn)單。它只提供了一個(gè)遍歷指針,在所有ID結果集中遍歷,然后也是通過(guò)ID獲得完整的數據對象。
總之,Jive中關(guān)于批量查詢(xún)有兩種實(shí)現方式:以ForumThreadBlockIterator為代表的實(shí)現方式適合在數據量巨大、需要多頁(yè)查詢(xún)時(shí)使用;而DatabaseObjectIterator則是推薦在一個(gè)頁(yè)面中顯示少量數據時(shí)使用。
裝飾(Decorator)模式是動(dòng)態(tài)給一個(gè)對象添加一些額外的職責,或者說(shuō)改變這個(gè)對象的一些行為。這就類(lèi)似于使用油漆為某個(gè)東西刷上油漆,在原來(lái)的對象表面增加了一層外衣。
在裝飾模式中,有兩個(gè)主要角色:一個(gè)是被刷油漆的對象(decoratee);另外一個(gè)是給decoratee刷油漆的對象(decorator)。這兩個(gè)對象都繼承同一個(gè)接口。
首先舉一個(gè)簡(jiǎn)單例子來(lái)說(shuō)明什么是裝飾模式。
先創(chuàng )建一個(gè)接口:
public interface Work
{
publicvoid insert();
}
這是一種打樁工作的抽象接口,動(dòng)作insert表示插入,那么插入什么?下面這個(gè)實(shí)現表示方形木樁的插入:
public class SquarePeg implementsWork{
publicvoid insert(){
System.out.println("方形樁插入");
}
}
本來(lái)這樣也許就可以滿(mǎn)足打樁的工作需要,但是有可能土質(zhì)很硬,在插入方形樁之前先要打一個(gè)洞,那么又將如何實(shí)現?可以編制一個(gè)Decorator類(lèi),同樣繼承Work接口,但是在實(shí)現insert方法時(shí)有些特別:
public class Decorator implementsWork{
privateWork work;
//額外增加的功能被打包在這個(gè)List中
privateArrayList others = new ArrayList();
publicDecorator(Work work)
{
this.work=work;
others.add("打洞"); //準備好額外的功能
}
publicvoid insert(){
otherMethod();
work.insert();
}
publicvoid otherMethod()
{
ListIteratorlistIterator = others.listIterator();
while(listIterator.hasNext())
{
System.out.println(((String)(listIterator.next()))+ " 正在進(jìn)行");
}
}
}
在Decorator的方法insert中先執行otherMethod()方法,然后才實(shí)現SquarePeg的insert方法。油漆工Decorator給被油漆者SquarePeg添加了新的行為——打洞。具體客戶(hù)端調用如下:
Work squarePeg = new SquarePeg();
Work decorator = new Decorator(squarePeg);
decorator.insert();
本例中只添加了一個(gè)新的行為(打洞),如果還有很多類(lèi)似的行為,那么使用裝飾模式的優(yōu)點(diǎn)就體現出來(lái)了。因為可以通過(guò)另外一個(gè)角度(如組織新的油漆工實(shí)現子類(lèi))來(lái)對這些行為進(jìn)行混合和匹配,這樣就不必為每個(gè)行為創(chuàng )建一個(gè)類(lèi),從而減少了系統的復雜性。
使用裝飾模式可以避免在被油漆對象decoratee中包裝很多動(dòng)態(tài)的,可能需要也可能不需要的功能,只要在系統真正運行時(shí),通過(guò)油漆工decorator來(lái)檢查那些需要加載的功能,實(shí)行動(dòng)態(tài)加載。
Jive論壇實(shí)現了信息過(guò)濾功能。例如可以將帖子內容中的HTML語(yǔ)句過(guò)濾掉;可以將帖子內容中Java代碼以特別格式顯示等。這些過(guò)濾功能有很多,在實(shí)際使用時(shí)不一定都需要,是由實(shí)際情況選擇的。例如有的論壇就不需要將帖子內容的HTML語(yǔ)句過(guò)濾掉,選擇哪些過(guò)濾功能是由論壇管理者具體動(dòng)態(tài)決定的。而且新的過(guò)濾功能可能隨時(shí)可以定制開(kāi)發(fā)出來(lái),如果試圖強行建立一種接口包含所有過(guò)濾行為,那么到時(shí)有新過(guò)濾功能加入時(shí),還需要改變接口代碼,真是一種危險的行為。
裝飾模式可以解決這種運行時(shí)需要動(dòng)態(tài)增加功能的問(wèn)題,且看看Jive是如何實(shí)現的。
前面討論過(guò),在Jive中,有主要幾個(gè)對象ForumFactory、Forum以及ForumThread和ForumMessage,它們之間的關(guān)系如圖3-2所示。因此帖子內容ForumMessage對象的獲得是從其上級FroumThread的方法getMessage中獲取,但是在實(shí)際代碼中,ForumThread的方法getMessage委托ForumFactory來(lái)獲取ForumMessage對象??纯?/span>ForumThread的子類(lèi)DbForumThread的getMessage代碼:
public ForumMessage getMessage(longmessageID)
throwsForumMessageNotFoundException
{
returnfactory.getMessage(messageID, this.id, forumID);
}
這是一種奇怪的委托,大概是因為需要考慮到過(guò)濾器功能有意為之吧。那就看看ForumFactory的具體實(shí)現子類(lèi)DbForumFactory的getMessage功能,getMessage是將數據庫中的ForumMessage對象經(jīng)由過(guò)濾器過(guò)濾一遍后輸出(注:因為原來(lái)的Jive的getMessage代碼考慮到可緩存或不可緩存的過(guò)濾,比較復雜,實(shí)際過(guò)濾功能都是可以緩存的,因此精簡(jiǎn)如下)。
protected ForumMessage getMessage(longmessageID, long threadID, long forumID)
throwsForumMessageNotFoundException
{
DbForumMessagemessage = cacheManager.messageCache.get(messageID);
//Do a security check to make sure the message comes fromthe thread.
if(message.threadID != threadID) {
thrownew ForumMessageNotFoundException();
}
ForumMessagefilterMessage = null;
try{
// 應用全局過(guò)濾器
filterMessage= filterManager.applyFilters(message);
Forumforum = getForum(forumID);
//應用本論壇過(guò)濾器
filterMessage= forum.getFilterManager().applyFilters(filterMessage);
}
catch(Exception e) { }
returnfilterMessage;
}
上面代碼實(shí)際是裝飾模式的客戶(hù)端調用代碼,DbForumMessage 的實(shí)例message是被油漆者decoratee。通過(guò)filterManager 或forum.getFilterManager()的applyFilter方法,將message實(shí)行了所有的過(guò)濾功能。這就類(lèi)似前面示例的下列語(yǔ)句:
Work decorator = new Decorator(squarePeg);
forum.getFilterManager()是從數據庫中獲取當前配置的所有過(guò)濾器類(lèi)。每個(gè)Forum都有一套自己的過(guò)濾器類(lèi),這是通過(guò)下列語(yǔ)句實(shí)現的:
FilterManager filterManager = new DbFilterManager();
在DbFilterManager 的類(lèi)變量ForumMessageFilter[] filters中保存著(zhù)所有的過(guò)濾器,applyFilters方法實(shí)行過(guò)濾如下:
public ForumMessage applyFilters(ForumMessagemessage) {
for (int i=0;i < filters.length; i++) {
if(filters[i] != null) {
message= filters[i].clone(message);
}
}
returnmessage;
}
而ForumMessageFilter是ForumMessage的另外一個(gè)子類(lèi),被油漆者DbForumMessage通過(guò)油漆工ForumMessageFilter增加了一些新的行為和功能(過(guò)濾),如圖3-6所示。

圖3-6 裝飾模式
這就組成了一個(gè)稍微復雜一點(diǎn)的裝飾模式。HTMLFilter實(shí)現了HTML代碼過(guò)濾功能,而JavaCodeHighLighter實(shí)現了Java代碼過(guò)濾功能,HTMLFilter代碼如下:
public class HTMLFilter extendsForumMessageFilter {
public ForumMessageFilterclone(ForumMessage message){
HTMLFilterfilter = new HTMLFilter();
filter.message= message;
returnfilter;
}
public booleanisCacheable() {
returntrue;
}
public StringgetSubject() {
returnStringUtils.escapeHTMLTags(message.getSubject());
}
public StringgetBody() {
returnStringUtils.escapeHTMLTags(message.getBody());
}
}
HTMLFilter中重載了ForumMessage的getSubject()、getBody()方法,實(shí)際是改變了這兩個(gè)原來(lái)的行為,這類(lèi)似前面舉例的方法:
public void insert(){
otherMethod();
work.insert();
}
這兩者都改變了被油漆者的行為。
在HTMLFilter中還使用了原型(Prototype)模式,原型模式定義是:用原型實(shí)例指定創(chuàng )建對象的種類(lèi),并且通過(guò)復制這些原型創(chuàng )建新的對象。按照這種定義,Java的clone技術(shù)應該是原型模式的一個(gè)實(shí)現。
HTMLFilter的clone方法實(shí)際就是在當前HTMLFilter實(shí)例中再生成一個(gè)同樣的實(shí)例。這樣在處理多個(gè)并發(fā)請求時(shí),不用通過(guò)同一個(gè)過(guò)濾器實(shí)例進(jìn)行處理,提高了性能。但是HTMLFilter的clone方法是采取new方法來(lái)實(shí)現,不如直接使用Object的native方法速度快。
因為在DbFilterManager中是根據配置使用類(lèi)反射機制動(dòng)態(tài)分別生成包括HTMLFilter在內的過(guò)濾器實(shí)例。但是每種過(guò)濾器實(shí)例只有一個(gè),為了使得大量用戶(hù)不必爭奪一個(gè)過(guò)濾器實(shí)例來(lái)實(shí)現過(guò)濾,就采取了克隆方式,這種實(shí)戰手法可以借鑒在自己的應用系統中。
觀(guān)察者(Observer)模式是定義對象之間一對多的依賴(lài)關(guān)系,當一個(gè)被觀(guān)察的對象發(fā)生改變時(shí),所有依賴(lài)于它的對象都會(huì )得到通知并采取相應行為。
使用觀(guān)察者模式的優(yōu)點(diǎn)是將被觀(guān)察者和觀(guān)察者解耦,從而可以不影響被觀(guān)察者繼續自己的行為動(dòng)作。觀(guān)察者模式適合應用于一些“事件觸發(fā)”場(chǎng)合。
在Jive中,用戶(hù)也許會(huì )對某個(gè)主題感興趣,希望關(guān)于此主題發(fā)生的任何新的討論能通過(guò)電子郵件通知他,因此他訂閱監視了這個(gè)主題。因為這個(gè)功能的實(shí)現會(huì )引入電子郵件的發(fā)送。在前面章節已經(jīng)討論了電子郵件發(fā)送有可能因為網(wǎng)絡(luò )原因延遲,如果在有人回復這個(gè)主題時(shí),立即進(jìn)行電子郵件發(fā)送,通知所有訂閱該主題的用戶(hù)。那么該用戶(hù)可能等待很長(cháng)時(shí)間得不到正?;貞?。
使用觀(guān)察者模式,可以通過(guò)觸發(fā)一個(gè)觀(guān)察者,由觀(guān)察者通過(guò)另外線(xiàn)程來(lái)實(shí)施郵件發(fā)送,而被觀(guān)察者發(fā)出觸發(fā)通知后,可以繼續自己原來(lái)的邏輯行為。
看看Jive的WatchManager類(lèi):
public interface WatchManager{
//正常監察類(lèi)型,用戶(hù)在這個(gè)主題更新后再次訪(fǎng)問(wèn)時(shí),會(huì )明顯地發(fā)現
public staticfinal int NORMAL_WATCH = 0;
// 當主題變化時(shí),通過(guò)電子郵件通知用戶(hù)
public staticfinal int EMAIL_NOTIFY_WATCH = 1;
//設置一個(gè)主題被觀(guān)察的時(shí)間,默認為30天
public voidsetDeleteDays(int deleteDays) throws UnauthorizedException;
public int getDeleteDays();
//是否激活了E-mail提醒
public booleanisEmailNotifyEnabled() throws UnauthorizedException;
public voidsetEmailNotifyEnabled(boolean enabled) throws UnauthorizedException;
//保存E-mail的內容
public StringgetEmailBody() throws UnauthorizedException;
public voidsetEmailBody(String body) throws UnauthorizedException;
//保存E-mail的主題
public StringgetEmailSubject() throws UnauthorizedException;
public void setEmailSubject(Stringsubject) throws UnauthorizedException;
…
//為某個(gè)主題創(chuàng )建一個(gè)觀(guān)察者
public voidcreateWatch(User user, ForumThread thread, int watchType)
throwsUnauthorizedException;
//刪除某個(gè)主題的觀(guān)察者
public voiddeleteWatch(User user, ForumThread thread, int watchType)
//得到一個(gè)主題的所有觀(guān)察者
public IteratorgetWatchedForumThreads(User user, int watchType)
throwsUnauthorizedException;
//判斷一個(gè)用戶(hù)是否在觀(guān)察監視該主題
public booleanisWatchedThread(User user, ForumThread thread, int watchType)
throwsUnauthorizedException;
…
}
DbWatchManager是WatchManager的一個(gè)子類(lèi),通過(guò)數據庫保存著(zhù)有關(guān)某個(gè)主題被哪些用戶(hù)監視等數據資料。WatchManager對象是隨同DbForumFactory()一起生成的。
在DbWatchManager中有一個(gè)WatchManager沒(méi)有的很重要的方法——通知方法:
protected void notifyWatches(ForumThreadthread) {
//If watchesare turned on.
if (!emailNotifyEnabled){
return;
}
//通知所有觀(guān)察這個(gè)主題的用戶(hù)
EmailWatchUpdateTasktask = new EmailWatchUpdateTask(this, factory, thread);
TaskEngine.addTask(task);
}
這個(gè)方法用來(lái)觸發(fā)所有有關(guān)這個(gè)主題的監視或訂閱用戶(hù),以E-mail發(fā)送提醒他們。那么這個(gè)通知方法本身又是如何被觸發(fā)的?從功能上分析,應該是在發(fā)表新帖子時(shí)觸發(fā)。
在DbForumThread的addMessage的最后一行有一句:
factory.watchManager.notifyWatches(this);
這其實(shí)是調用了DbWatchManager的notifyWatches方法,因此確實(shí)是在增加新帖子時(shí)觸發(fā)了該帖子的所有觀(guān)察者。
notifyWatches方法中在執行E-mail通知用戶(hù)時(shí),使用了TaskEngine來(lái)執行E-mail發(fā)送。E-mailWatchUpdateTask是一個(gè)線(xiàn)程類(lèi),而TaskEngine是線(xiàn)程任務(wù)管理器,專(zhuān)門(mén)按要求啟動(dòng)如E-mailWatchUpdateTask這樣的任務(wù)線(xiàn)程。其實(shí)TaskEngine是一個(gè)簡(jiǎn)單的線(xiàn)程池,它不斷通過(guò)查詢(xún)Queue是否有可運行的線(xiàn)程,如果有就直接運行線(xiàn)程。
public class TaskEngine {
//任務(wù)列表
private staticLinkedList taskList = null;
//工作數組
private staticThread[] workers = null;
private staticTimer taskTimer = null;
private staticObject lock = new Object();
static {
//根據配置文件初始化任務(wù)啟動(dòng)時(shí)間
taskTimer= new Timer(true);
// 默認使用7個(gè)線(xiàn)程來(lái)裝載啟動(dòng)任務(wù)
workers= new Thread[7];
taskList= new LinkedList();
for(int i=0; i<workers.length; i++) {
//TaskEngineWorker是個(gè)簡(jiǎn)單的線(xiàn)程類(lèi)
TaskEngineWorkerworker = new TaskEngineWorker();
workers[i]= new Thread(worker);
workers[i].setDaemon(true);
workers[i].start(); //啟動(dòng)TaskEngineWorker這個(gè)線(xiàn)程
}
}
//TaskEngineWorker內部類(lèi)
private staticclass TaskEngineWorker implements Runnable {
privateboolean done = false;
publicvoid run() {
while(!done) {
//運行nextTask方法
nextTask().run();
}
}
}
// nextTask()返回的是一個(gè)可運行線(xiàn)程,是任務(wù)列表Queue的一個(gè)讀取者
private staticRunnable nextTask() {
synchronized(lock){
// 如果沒(méi)有任務(wù),就鎖定在這里
while(taskList.isEmpty()) {
try{
lock.wait(); //等待解鎖
}catch (InterruptedException ie) { }
}
//從任務(wù)列表中取出第一個(gè)任務(wù)線(xiàn)程
return(Runnable)taskList.removeLast();
}
}
public staticvoid addTask(Runnable r) {
addTask(r,Thread.NORM_PRIORITY);
}
//這是任務(wù)列表Queue的生產(chǎn)者
public staticvoid addTask(Runnable task, int priority) {
synchronized(lock){
taskList.addFirst(task);
//提醒所有鎖在lock這里的線(xiàn)程可以運行了
//這是線(xiàn)程的互相通知機制,可參考線(xiàn)程參考資料
lock.notifyAll();
}
}
…
}
在TaskEngine中啟動(dòng)設置了一個(gè)消息管道Queue和兩個(gè)線(xiàn)程。一個(gè)線(xiàn)程是負責向Queue里放入Object,可謂是消息的生產(chǎn)者;而另外一個(gè)線(xiàn)程負責從Queue中取出Object,如果Queue中沒(méi)有Object,那它就鎖定(Block)在那里,直到Queue中有Object,因為這些Object本身也是線(xiàn)程,因此它取出后就直接運行它們。
這個(gè)TaskEngine建立的模型非常類(lèi)似JMS(Java消息系統),雖然它們功能類(lèi)似,但不同的是:JMS是一個(gè)分布式消息發(fā)布機制,可以在多臺服務(wù)器上運行,處理能力要強大得多。而TaskEngine由于基于線(xiàn)程基礎,因此不能跨JVM實(shí)現??梢哉f(shuō)TaskEngine是一個(gè)微觀(guān)組件,而JMS則是一個(gè)宏觀(guān)架構系統。JMS相關(guān)討論將在后面章節進(jìn)行。
以上討論了Jive系統中觀(guān)察者模式的實(shí)現,Jive使用線(xiàn)程比較基礎的概念實(shí)現了觀(guān)察者模式,當然有助于了解J2EE很多底層的基礎知識,整個(gè)Web容器的技術(shù)實(shí)現就是基于線(xiàn)程池原理建立的。
Java的JDK則提供了比較方便的觀(guān)察者模式API——java.util.Observable和java.util.Observer,它們的用戶(hù)非常簡(jiǎn)單,只要被觀(guān)察者繼承Observable,然后使用下列語(yǔ)句設置觀(guān)察點(diǎn):
setChanged();
notifyObservers(name); //一旦執行本代碼,就觸發(fā)觀(guān)察者了
而觀(guān)察者只要實(shí)現Observer接口,并實(shí)現update方法,在update方法中將被觀(guān)察者觸發(fā)后傳來(lái)的object進(jìn)行處理。舉例如下:
網(wǎng)上商店中商品價(jià)格可能發(fā)生變化,如果需要在價(jià)格變化時(shí),首頁(yè)能夠自動(dòng)顯示這些降價(jià)產(chǎn)品,那么使用觀(guān)察者模式將方便得多。首先,商品是一個(gè)被觀(guān)察者:
public class product extends Observable{
privatefloat price;
publicfloat getPrice(){ return price;}
publicvoid setPrice(){
this.price=price;
//商品價(jià)格發(fā)生變化,觸發(fā)觀(guān)察者
setChanged();
notifyObservers(newFloat(price));
}
...
}
價(jià)格觀(guān)察者實(shí)現observer接口:
public class PriceObserver implementsObserver{
privatefloat price=0;
publicvoid update(Observable obj,Object arg){
if(arg instanceof Float){
price=((Float)arg).floatValue();
System.out.println("PriceObserver:price changet to "+price);
}
}
}
這樣,一個(gè)簡(jiǎn)單的觀(guān)察者模式就很容易地實(shí)現了。
聯(lián)系客服