2? Jive
與設(shè)計(jì)模式
Jive
論壇系統(tǒng)使用大量設(shè)計(jì)模式巧妙地實(shí)現(xiàn)了一系列功能。因?yàn)樵O(shè)計(jì)模式的通用性和可理解性,將幫助更多人很快地理解
Jive
論壇源碼,從而可以依據(jù)一種“協(xié)定”來動(dòng)態(tài)地?cái)U(kuò)展它。那么使用設(shè)計(jì)模式還有哪些好處?
2.1?
設(shè)計(jì)模式
設(shè)計(jì)模式是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過分類編目的、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)。使用設(shè)計(jì)模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。毫無疑問,設(shè)計(jì)模式于己于他人于系統(tǒng)都是多贏的。設(shè)計(jì)模式使代碼編制真正工程化,設(shè)計(jì)模式是軟件工程的基石。
GOF
(設(shè)計(jì)模式作者簡(jiǎn)稱)《設(shè)計(jì)模式》這本書第一次將設(shè)計(jì)模式提升到理論高度,并將之規(guī)范化,該書提出了
23
種基本設(shè)計(jì)模式。自此,在可復(fù)用面向?qū)ο筌浖陌l(fā)展過程中,新的大量的設(shè)計(jì)模式不斷出現(xiàn)。
很多人都知道
Java
是完全面向?qū)ο蟮脑O(shè)計(jì)和編程語(yǔ)言,但是由于接受教育以及經(jīng)驗(yàn)的原因,大多數(shù)程序員或設(shè)計(jì)人員都是從傳統(tǒng)的過程語(yǔ)言轉(zhuǎn)變而來,因此在思維習(xí)慣上要完全轉(zhuǎn)變?yōu)槊嫦驅(qū)ο蟮脑O(shè)計(jì)和開發(fā)方式是困難的,而學(xué)習(xí)設(shè)計(jì)模式可以更好地幫助和堅(jiān)固這種轉(zhuǎn)變。
凡是學(xué)習(xí)完成設(shè)計(jì)模式的人都有一種類似重生的感覺,這種重生可以從很多方面去解釋。換一種新的角度來看待和解決問題應(yīng)該是一種比較貼切的解釋,而這種新的思維角度培養(yǎng)屬于基礎(chǔ)培訓(xùn),因此,設(shè)計(jì)模式是學(xué)習(xí)
Java
的必讀基礎(chǔ)課程之一。
由于設(shè)計(jì)模式概念比較抽象,對(duì)于初學(xué)者學(xué)習(xí)有一定的難度,因此結(jié)合
Jive
論壇系統(tǒng)學(xué)習(xí)設(shè)計(jì)模式將是一種很好的選擇。
掌握了設(shè)計(jì)模式,將會(huì)幫助程序員或設(shè)計(jì)人員以更加可重用性、可伸縮性的眼光來開發(fā)應(yīng)用系統(tǒng),甚至開發(fā)通用的框架系統(tǒng)。框架系統(tǒng)是構(gòu)成一類特定軟件可復(fù)用設(shè)計(jì)的一組相互協(xié)作的類,主要是對(duì)應(yīng)用系統(tǒng)中反復(fù)重用部分的提煉,類似一種模板,這是一種結(jié)構(gòu)性的模板。
框架通常定義了應(yīng)用體系的整體結(jié)構(gòu)、類和對(duì)象的關(guān)系等設(shè)計(jì)參數(shù),以便于具體應(yīng)用實(shí)現(xiàn)者能集中精力于應(yīng)用本身的特定細(xì)節(jié)。框架強(qiáng)調(diào)設(shè)計(jì)復(fù)用,而設(shè)計(jì)模式最小的可重用單位,因此框架不可避免地會(huì)反復(fù)使用到設(shè)計(jì)模式。關(guān)于通用框架系統(tǒng)的設(shè)計(jì)開發(fā)將在以后章節(jié)中討論。
其實(shí)
Jive
論壇本身也形成了一個(gè)基于
Web
結(jié)構(gòu)的通用框架系統(tǒng),因?yàn)樗芏嘣O(shè)計(jì)思想是可以重用的,例如設(shè)定一個(gè)總體入口,通過入口檢查用戶的訪問控制權(quán)限,當(dāng)然還有其他各方面的功能實(shí)現(xiàn)方式都是值得在其他系統(tǒng)中借鑒的,也正因?yàn)樗阅J降男问奖憩F(xiàn)出來,這種可重用性和可借鑒性就更強(qiáng)。
工廠模式是
GOF
設(shè)計(jì)模式的主要常用模式,它主要是為創(chuàng)建對(duì)象提供了一種接口,工廠模式主要是封裝了創(chuàng)建對(duì)象的細(xì)節(jié)過程,從而使得外界調(diào)用一個(gè)對(duì)象時(shí),根本無需關(guān)心這個(gè)對(duì)象是如何產(chǎn)生的。
在
GOF
設(shè)計(jì)模式中,工廠模式分為工廠方法模式和抽象工廠模式。兩者主要區(qū)別是,工廠方法是創(chuàng)建一種產(chǎn)品接口下的產(chǎn)品對(duì)象,而抽象工廠模式是創(chuàng)建多種產(chǎn)品接口下的產(chǎn)品對(duì)象,非常類似
Builder
生成器模式。在平時(shí)實(shí)踐中,使用較多的基本是工廠方法模式。
以類
SampleOne
為例,要?jiǎng)?chuàng)建
SampleOne
的對(duì)象實(shí)例
:
SampleOne sampleOne = new SampleOne();
如果
Sample
類有幾個(gè)相近的類:
SampleTwo
或
SampleThree
,那么創(chuàng)建它們的實(shí)例分別是:
SampleTwo sampleTwo = new SampleTwo();
SampleThree sampleThree = new SampleThree();
其實(shí)這
3
個(gè)類都有一些共同的特征,如網(wǎng)上商店中銷售書籍、玩具或者化妝品。雖然它們是不同的具體產(chǎn)品,但是它們有一個(gè)共同特征,可以抽象為“商品”。日常生活中很多東西都可以這樣高度抽象成一種接口形式。上面這
3
個(gè)類如果可以抽象為一個(gè)統(tǒng)一接口
SampleIF
,那么上面語(yǔ)句就可以成為:
SampleIF sampleOne = new SampleOne();
SampleIF sampleTwo = new SampleTwo();
SampleIF sampleThree = new SampleThree();
在實(shí)際情況中,有時(shí)并不需要同時(shí)生成
3
種對(duì)象,而是根據(jù)情況在
3
者之中選一個(gè)。在這種情況下,需要使用工廠方法來完成了,創(chuàng)建一個(gè)叫
SampleFactory
的抽象類:
public class SampleFactory{
?? public abstract SampleIF creator();
}
在這個(gè)抽象工廠類中有一個(gè)抽象方法
creator
,但是沒有具體實(shí)現(xiàn),而是延遲到它的子類中實(shí)現(xiàn),創(chuàng)建子類
SampleFactoryImp
:
public class SampleFactoryImp extends SampleFactory{
?? public SampleIF creator(){
??? //
根據(jù)其他因素綜合判斷返回具體產(chǎn)品
??? //
假設(shè)應(yīng)該返回
SampleOne
對(duì)象
?????? return new SampleOne();
}
}
在
SampleFactoryImp
中根據(jù)具體情況來選擇返回
SampleOne
、
SampleTwo
或
SampleThree
。所謂具體情況有很多種:上下文其他過程計(jì)算結(jié)果;直接根據(jù)配置文件中配置。
上述工廠方法模式中涉及到一個(gè)抽象產(chǎn)品接口
Sample
,如果還有其他完全不同的產(chǎn)品接口,如
Product
等,一個(gè)子類
SampleFactoryImp
只能實(shí)現(xiàn)一套系列產(chǎn)品方案的生產(chǎn),如果還需要另外一套系統(tǒng)產(chǎn)品方案,就可能需要另外一個(gè)子類
SampleFactoryImpTwo
來實(shí)現(xiàn)。這樣,多個(gè)產(chǎn)品系列、多個(gè)工廠方法就形成了抽象工廠模式。
前面已經(jīng)討論在
Jive
中設(shè)置了論壇統(tǒng)一入口,這個(gè)統(tǒng)一入口就是
ForumFactory
,以下是
ForumFactory
的主要代碼:
public abstract class ForumFactory {
private static Object initLock = new Object();
private static String className = " com.Yasna.forum.database.DbForumFactory";
private static ForumFactory factory = null;
??
public static ForumFactory getInstance(Authorization authorization) {
if (authorization == null) {
return null;
}
//
以下使用了
Singleton
單態(tài)模式,將在
2.3
節(jié)討論
if (factory == null) {
synchronized(initLock) {
if (factory == null) {
... //
從配置文件中獲得當(dāng)前
className
try {
//
動(dòng)態(tài)裝載類
Class c = Class.forName(className);
factory = (ForumFactory)c.newInstance();
}
catch (Exception e) {
return null;
}
}
}
}
//
返回
proxy.
用來限制授權(quán)對(duì)
forum
的訪問
return new ForumFactoryProxy(authorization, factory,factory.getPermissions(authorization));
}
//
創(chuàng)鍵產(chǎn)品接口
Forum
的具體對(duì)象實(shí)例
public abstract Forum createForum(String name, String description)
throws UnauthorizedException, ForumAlreadyExistsException;
?? //
創(chuàng)鍵產(chǎn)品接口
ForumThread
的具體對(duì)象實(shí)例
public abstract ForumThread createThread(ForumMessage rootMessage)
throws UnauthorizedException;
//
創(chuàng)鍵產(chǎn)品接口
ForumMessage
的具體對(duì)象實(shí)例
?
?
??? public abstract ForumMessage createMessage();
....
}
ForumFactory
中提供了很多抽象方法如
createForum
、
createThread
和
createMessage()
等,它們是創(chuàng)建各自產(chǎn)品接口下的具體對(duì)象,這
3
個(gè)接口就是前面分析的基本業(yè)務(wù)對(duì)象
Forum
、
ForumThread
和
ForumMessage
,這些創(chuàng)建方法在
ForumFactory
中卻不立即執(zhí)行,而是推遲到
ForumFactory
子類中實(shí)現(xiàn)。
ForumFactory
的子類實(shí)現(xiàn)是
com.Yasna.forum.database.DbForumFactory
,這是一種數(shù)據(jù)庫(kù)實(shí)現(xiàn)方式。即在
DbForumFactory
中分別實(shí)現(xiàn)了在數(shù)據(jù)庫(kù)中
createForum
、
createThread
和
createMessage()
等
3
種方法,當(dāng)然也提供了動(dòng)態(tài)擴(kuò)展到另外一套系列產(chǎn)品的生產(chǎn)方案的可能。如果使用
XML
來實(shí)現(xiàn),那么可以編制一個(gè)
XmlForumFactory
的具體工廠子類來分別實(shí)現(xiàn)
3
種創(chuàng)建方法。
因此,
Jive
論壇在統(tǒng)一入口處使用了抽象工廠模式來動(dòng)態(tài)地創(chuàng)建論壇中所需要的各種產(chǎn)品,如圖
3-4
所示。
圖
3-4? ForumFactory
抽象工廠模式圖
圖
3-4
中
,
XmlForumFactory
和
DbForumFactory
作為抽象工廠
ForumFactory
的兩個(gè)具體實(shí)現(xiàn)
,
而
Forum
、
ForumThread
和
ForumMessage
分別作為
3
個(gè)系列抽象產(chǎn)品接口
,
依靠不同的工廠實(shí)現(xiàn)方式
,
會(huì)產(chǎn)生不同的產(chǎn)品對(duì)象。
從抽象工廠模式去理解
Jive
論壇統(tǒng)一入口處,可以一步到位掌握了幾個(gè)類之間的大概關(guān)系。因?yàn)槭褂昧顺橄蠊S模式這種通用的設(shè)計(jì)模式,可以方便源碼閱讀者快速地掌握整個(gè)系統(tǒng)的結(jié)構(gòu)和來龍去脈,圖
3-4
這張圖已經(jīng)初步展示了
Jive
的主要框架結(jié)構(gòu)。
細(xì)心的讀者也許會(huì)發(fā)現(xiàn),在上面
ForumFactory
有一個(gè)
getInstance
比較令人費(fèi)解,這將在
2.3
節(jié)進(jìn)行討論。
2.3?
統(tǒng)一入口與單態(tài)模式
在上面
ForumFactory
的
getInstance
方法使用單態(tài)(
SingleTon
)模式。單態(tài)模式是保證一個(gè)類有且僅有一個(gè)對(duì)象實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn)。
前面曾提到
ForumFactory
是
Jive
提供客戶端訪問數(shù)據(jù)庫(kù)系統(tǒng)的統(tǒng)一入口。為了保證所有的客戶端請(qǐng)求都要經(jīng)過這個(gè)
ForumFactory
,如果不使用單態(tài)模式,客戶端下列調(diào)用語(yǔ)句表示生成了
ForumFactory
實(shí)例:
ForumFactory factory = new DbForumFactory();
客戶端每發(fā)生一次請(qǐng)求都調(diào)用這條語(yǔ)句,這就會(huì)發(fā)生每次都生成不同
factory
對(duì)象實(shí)例,這顯然不符合設(shè)計(jì)要求,因此必須使用單態(tài)模式。
一般在
Java
實(shí)現(xiàn)單態(tài)模式有幾種選擇,最常用而且安全的用法如下:
public class Singleton {
private Singleton(){}
//
在自己內(nèi)部定義自己一個(gè)實(shí)例,是不是很奇怪
//
注意這是
private
,只供內(nèi)部調(diào)用
private static Singleton instance = new Singleton();
//
這里提供了一個(gè)供外部訪問本
class
的靜態(tài)方法,可以直接訪問
public static Singleton getInstance() {
return instance;
}
}
單態(tài)模式一共使用了兩條語(yǔ)句實(shí)現(xiàn):第一條直接生成自己的對(duì)象,第二條提供一個(gè)方法供外部調(diào)用這個(gè)對(duì)象,同時(shí)最好將構(gòu)造函數(shù)設(shè)置為
private
,以防止其他程序員直接使用
new Singleton
生成實(shí)例。
還有一種
Java
單態(tài)模式實(shí)現(xiàn):
public class Singleton {
private Singleton(){}
private static Singleton instance = null;
public static synchronized Singleton getInstance() {
if (instance==null)
instance
=
new Singleton()
return instance;
}
}
在上面代碼中,使用了判斷語(yǔ)句。如果
instance
為空,再進(jìn)行實(shí)例化,這成為
lazy initialization
。注意
getInstance()
方法的
synchronized
,這個(gè)
synchronized
很重要。如果沒有
synchronized
,那么使用
getInstance()
在第一次被訪問時(shí)有可能得到多個(gè)
Singleton
實(shí)例。
關(guān)于
lazy initialization
的
Singleton
有很多涉及
double-checked locking (DCL)
的討論,有興趣者可以進(jìn)一步研究。一般認(rèn)為第一種形式要更加安全些;但是后者可以用在類初始化時(shí)需要參數(shù)輸入的情況下。
在
Jive
的
ForumFactory
中采取了后者
lazy initialization
形式,這是為了能夠動(dòng)態(tài)配置指定
ForumFactory
的具體子類。在
getInstance
中,從配置文件中獲得當(dāng)前工廠的具體實(shí)現(xiàn),如果需要啟動(dòng)
XmlForumFactory
,就不必修改
ForumFactory
代碼,直接在配置文件中指定
className
的名字為
XmlForumFactory
。這樣通過下列動(dòng)態(tài)裝載機(jī)制生成
ForumFactory
具體對(duì)象:
Class c = Class.forName(className);
factory = (ForumFactory)c.newInstance();
這是利用
Java
的反射機(jī)制,可以通過動(dòng)態(tài)指定
className
的數(shù)值而達(dá)到生成對(duì)象的方式。
使用單態(tài)模式的目標(biāo)是為了控制對(duì)象的創(chuàng)建,單態(tài)模式經(jīng)常使用在控制資源的訪問上。例如數(shù)據(jù)庫(kù)連接或
Socket
連接等。單態(tài)模式可以控制在某個(gè)時(shí)刻只有一個(gè)線程訪問資源。由于
Java
中沒有全局變量的概念,因此使用單態(tài)模式有時(shí)可以起到這種作用,當(dāng)然需要注意是在一個(gè)
JVM
中。
2.4?
訪問控制與代理模式
仔細(xì)研究會(huì)發(fā)現(xiàn),在
ForumFactory
的
getInstance
方法中最后的返回值有些奇怪。按照單態(tài)模式的概念應(yīng)該直接返回
factory
這個(gè)對(duì)象實(shí)例,但是卻返回了
ForumFactoryProxy
的一個(gè)實(shí)例,這實(shí)際上改變了單態(tài)模式的初衷。這樣客戶端每次通過調(diào)用
ForumFactory
的
getInstance
返回的就不是
ForumFactory
的惟一實(shí)例,而是新的對(duì)象。之所以這樣做是為了訪問權(quán)限的控制,姑且不論這樣做的優(yōu)劣,先看看什么是代理模式。
代理模式是屬于設(shè)計(jì)模式結(jié)構(gòu)型模式中一種,它是實(shí)際訪問對(duì)象的代理對(duì)象,或者影子對(duì)象,主要達(dá)到控制實(shí)際對(duì)象的訪問。這種控制的目的很多,例如提高性能等。即遠(yuǎn)程代理模式,這種模式將在以后章節(jié)討論。
其中一個(gè)主要的控制目的是控制客戶端對(duì)實(shí)際對(duì)象的訪問權(quán)限。在
Jive
系統(tǒng)中,因?yàn)橛薪巧珯?quán)限的分別,對(duì)于
Forum
、
ForumThread
和
FroumMessage
的訪問操作必須經(jīng)過權(quán)限機(jī)制驗(yàn)證后才能進(jìn)行。
以
ForumFactoryProxy
中的
createForum
方法為例,其實(shí)
ForumFactoryProxy
也是
FroumFactory
的一種工廠實(shí)現(xiàn),它的
createForum
具體實(shí)現(xiàn)如下:
public Forum createForum(String name, String description)
??????????? throws UnauthorizedException, ForumAlreadyExistsException
??? {
??????? if (permissions.get(ForumPermissions.SYSTEM_ADMIN)) {
??????????? Forum newForum = factory.createForum(name, description);
??????????? return new ForumProxy(newForum, authorization, permissions);
??????? }
??????? else {
??????????? throw new UnauthorizedException();
??????? }
}
在這個(gè)方法中進(jìn)行了權(quán)限驗(yàn)證,判斷是否屬于系統(tǒng)管理員。如果是,將直接從
DbForumFactory
對(duì)象
factory
的方法
createForum
中獲得一個(gè)新的
Forum
對(duì)象,然后再返回
Forum
的子類代理對(duì)象
ForumProxy
。因?yàn)樵?/span>
Forum
中也還有很多屬性和操作方法,這些也需要進(jìn)行權(quán)限驗(yàn)證。
ForumProxy
和
ForumFactoryProxy
起到類似的作用。
Jive
中有下列幾個(gè)代理類:
·?????????
ForumFactoryProxy
:客戶端和
DbForumFactory
之間的代理。客戶端訪問
DbForumFactory
的任何方法都要先經(jīng)過
ForumFactoryProxy
相應(yīng)方法代理一次。以下意思相同。
·?????????
ForumProxy
:客戶端和
DbForum
之間的代理,研究
Forum
對(duì)象的每個(gè)方法,必須先看
ForumProxy
對(duì)象的方法。
·?????????
ForumMessageProxy
:客戶端和
DbForumMessage
之間的代理。
·?????????
ForumThreadProxy
:客戶端和
DbForumThread
之間的代理。
User
和
Group
也有相應(yīng)的代理類。
由以上分析看出,每個(gè)數(shù)據(jù)對(duì)象都有一個(gè)代理。如果系統(tǒng)中數(shù)據(jù)對(duì)象非常多,依據(jù)這種一對(duì)一的代理關(guān)系,會(huì)有很多代理類,將使系統(tǒng)變得不是非常干凈,因此可以使用動(dòng)態(tài)代理來代替這所有的代理類,具體實(shí)現(xiàn)將在以后章節(jié)討論。
2.5?
批量分頁(yè)查詢與迭代模式
迭代(
Iterator
)模式是提供一種順序訪問某個(gè)集合各個(gè)元素的方法,確保不暴露該集合的內(nèi)部表現(xiàn)。迭代模式應(yīng)用于對(duì)大量數(shù)據(jù)的訪問,
Java Collection API
中
Iterator
就是迭代模式的一種實(shí)現(xiàn)。
在前面章節(jié)已經(jīng)討論過,用戶查詢大量數(shù)據(jù),從數(shù)據(jù)庫(kù)不應(yīng)該直接返回
ResultSet
,應(yīng)該是
Collection
。但是有一個(gè)問題,如果這個(gè)數(shù)據(jù)很大,需要分頁(yè)面顯示。如果一下子將所有頁(yè)面要顯示的數(shù)據(jù)都查詢出來放在
Collection
,會(huì)影響性能。而使用迭代模式則不必將全部集合都展現(xiàn)出來,只有遍歷到某個(gè)元素時(shí)才會(huì)查詢數(shù)據(jù)庫(kù)獲得這個(gè)元素的數(shù)據(jù)。
以論壇中顯示帖子主題為例,在一個(gè)頁(yè)面中不可能顯示所有主題,只有分頁(yè)面顯示,如圖
3-5
所示。
圖
3-5
中一共分
15
頁(yè)來顯示所有論壇帖子,可以從顯示
Forum.jsp
中發(fā)現(xiàn)下列語(yǔ)句可以完成上述結(jié)果:
ResultFilter filter = new ResultFilter();? //
設(shè)置結(jié)果過濾器
?? filter.setStartIndex(start);???????????? //
設(shè)置開始點(diǎn)
?? filter.setNumResults(range);????????? //
設(shè)置范圍
?? ForumThreadIterator threads = forum.threads(filter);? //
獲得迭代器
?? while(threads.hasNext){
?????? //
逐個(gè)顯示
threads
中帖子主題,輸出圖
3-5
中的每一行
?? }
圖
3-5?
分頁(yè)顯示所有帖子
上述代碼中主要是從
Forum
的
threads
方法獲得迭代器
ForumThreadIterator
的實(shí)例
,
依據(jù)前面代理模式中分析、研究
Forum
對(duì)象的方法
,
首先是看
ForumProxy
中對(duì)應(yīng)方法
,
然后再看
DbForum
中對(duì)應(yīng)方法的具體實(shí)現(xiàn)。在
ForumProxy
中
,
threads
方法如下
:
public ForumThreadIterator threads(ResultFilter resultFilter) {
???? ForumThreadIterator iterator = forum.threads(resultFilter);
????? return new ForumThreadIteratorProxy(iterator, authorization, permissions);
}
首先是調(diào)用了
DbForum
中具體的
threads
方法,再追蹤到
DbForum
中看看,它的
threads
方法代碼如下:
public ForumThreadIterator threads(ResultFilter resultFilter) {
//
按
resultFilter
設(shè)置范圍要求獲得
SQL
查詢語(yǔ)句
?? String query = getThreadListSQL(resultFilter, false);?
?? //
獲得
resultFilter
設(shè)置范圍內(nèi)的所有
ThreadID
集合
?? long [] threadBlock = getThreadBlock(query.toString(), resultFilter.getStartIndex());
?? //
以下是計(jì)算查詢區(qū)域的開始點(diǎn)和終點(diǎn)
?? int startIndex = resultFilter.getStartIndex();
?? int endIndex;
?? // If number of results is set to inifinite, set endIndex to the total
? // number of threads in the 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è)查詢結(jié)果類,可以對(duì)論壇主題
Thread
和帖子內(nèi)容
Message
進(jìn)行過濾或排序,這樣就可以根據(jù)用戶要求定制特殊的查詢范圍。如查詢某個(gè)用戶去年在這個(gè)論壇發(fā)表的所有帖子,那只要?jiǎng)?chuàng)建一個(gè)
ResultFilter
對(duì)象就可以代表這個(gè)查詢要求。
在上面
threads
方法代碼中,第一步是先定制出相應(yīng)的動(dòng)態(tài)
SQL
查詢語(yǔ)句,然后使用這個(gè)查詢語(yǔ)句查詢數(shù)據(jù)庫(kù),獲得查詢范圍內(nèi)所有的
ForumThread
的
ID
集合,然后在這個(gè)
ID
集合中獲得當(dāng)前頁(yè)面的
ID
子集合,這是非常關(guān)鍵的一步。
在這關(guān)鍵的一步中,有兩個(gè)重要的方法
getThreadListSQL
和
getThreadBlock
:
·?????????
GetThreadListSQL
:獲得
SQL
查詢語(yǔ)句
query
的值,這個(gè)方法
Jive
實(shí)現(xiàn)起來顯得非常地瑣碎。
·?????????
GetThreadBlock
:獲得當(dāng)前頁(yè)面的
ID
子集合,那么如何確定
ID
子集合的開始位置呢?查看
getThreadBlock
方法代碼,可以發(fā)現(xiàn),它是使用最普遍的
ResultSet next()
方法來逐個(gè)跳躍到開始位置。
上面代碼的
Threads
方法中最后返回的是
ForumThreadBlockIterator
,它是抽象類
ForumThreadIterator
的子類,而
ForumThreadIterator
繼承了
Collection
的
Iterator
,以此聲明自己是一個(gè)迭代器,
ForumMessageBlockIterator
實(shí)現(xiàn)的具體方法如下:
public boolean hasNext();???? //
判斷是否有下一個(gè)元素
public boolean hasPrevious()? //
判斷是否有前一個(gè)元素
public Object next() throws java.util.NoSuchElementException? //
獲得下一個(gè)元素實(shí)例
ForumThreadBlockIterator
中的
Block
是“頁(yè)”的意思,它的一個(gè)主要類變量
threadBlock
包含的是一個(gè)頁(yè)面中所有
ForumThread
的
ID
,
next()
方法實(shí)際是對(duì)
threadBlock
中
ForumThread
進(jìn)行遍歷,如果這個(gè)頁(yè)面全部遍歷完成,將再獲取下一頁(yè)(
Block
)數(shù)據(jù)。
在
ForumThreadBlockIterator
重要方法
getElement
中實(shí)現(xiàn)了兩個(gè)功能:
·?????????
如果當(dāng)前遍歷指針超過當(dāng)前頁(yè)面,將使用
getThreadBlock
獲得下一個(gè)頁(yè)面的
ID
子集合;
·?????????
如果當(dāng)前遍歷指針在當(dāng)前頁(yè)面之內(nèi),根據(jù)
ID
獲得完整的數(shù)據(jù)對(duì)象,實(shí)現(xiàn)輸出;
ForumThreadBlockIterator
的
getElement
方法代碼如下:
private Object getElement(int index) {
?? if (index < 0) {??????? return null;??????? }
?? //
檢查所要獲得的
element
是否在本查詢范圍內(nèi)(當(dāng)前頁(yè)面內(nèi))
?? if (index < blockStart ||
index >= blockStart + DbForum.THREAD_BLOCK_SIZE) {??
????? try {
????????? //
從緩沖中獲得
Forum
實(shí)例
????????? DbForum forum = factory.cacheManager.forumCache.get(forumID);
????????? //
獲得下一頁(yè)的內(nèi)容
????????? this.threadBlock = forum.getThreadBlock(query, index);
????????? this.blockID = index / DbForum.THREAD_BLOCK_SIZE;
????????? this.blockStart = blockID * DbForum.THREAD_BLOCK_SIZE;
????? } catch (ForumNotFoundException fnfe) {
?????????????? return null;
?????? }
???? }
???? Object element = null;
???? //
計(jì)算這個(gè)元素在當(dāng)前查詢范圍內(nèi)的相對(duì)位置
???? int relativeIndex = index % DbForum.THREAD_BLOCK_SIZE;
???? // Make sure index isn't too large
???? if (relativeIndex < threadBlock.length) {
??????? try {
??????????? //
從緩沖中獲得實(shí)際
thread
對(duì)象
??????????? element = factory.cacheManager.threadCache.get(
?????????? ?????????????threadBlock[relativeIndex]);
??????? } catch (ForumThreadNotFoundException tnfe) { }
???? }
???? return element;
}
ForumThreadBlockIterator
是真正實(shí)現(xiàn)分頁(yè)查詢的核心功能,
ForumThreadBlockIterator
對(duì)象返回到客戶端的過程中,遭遇
ForumThreadIteratorProxy
的截獲,可以回頭看看
ForumProxy
中的
threads
方法,它最終返回給調(diào)用客戶端
Forum.jsp
的是
ForumThreadIteratorProxy
實(shí)例。
ForumThreadIteratorProxy
也是迭代器
ForumThreadIterator
的一個(gè)子類,它的一個(gè)具體方法中:
public Object next() {
? return new ForumThreadProxy((ForumThread)iterator.next(), authorization,
??????????? permissions);
}
這一句是返回一個(gè)
ForumThreadProxy
實(shí)例,返回就是一個(gè)
ForumThread
實(shí)例的代理。這里,
Jive
使用代理模式實(shí)現(xiàn)訪問控制實(shí)現(xiàn)得不是很巧妙,似乎有代理到處“飛”的感覺,這是可以對(duì)之進(jìn)行改造的。
從以上可以看出,
Jive
在輸出如圖
3-5
所示的多頁(yè)查詢結(jié)果時(shí),采取了下列步驟:
(
1
)先查詢出符合查詢條件的所有對(duì)象元素的
ID
集合,注意不是所有對(duì)象元素,只是其
ID
的集合,這樣節(jié)約了大量?jī)?nèi)存。
(
2
)每個(gè)頁(yè)面視為一個(gè)
Block
,每當(dāng)進(jìn)入下一頁(yè)時(shí),獲得下一個(gè)頁(yè)面的所有對(duì)象的
ID
集合。
(
3
)輸出當(dāng)前頁(yè)面的所有對(duì)象時(shí),首先從緩沖中獲取,如果緩沖中沒有,再根據(jù)
ID
從數(shù)據(jù)庫(kù)中獲取完整的對(duì)象數(shù)據(jù)。
上述實(shí)現(xiàn)方法完全基于即查即顯,相比于一般批量查詢做法:一次性獲得所有數(shù)據(jù),然后遍歷數(shù)據(jù)結(jié)果集
ResultSet
,
Jive
這種批量查詢方式是一種比較理想的選擇。
以上是
ForumThread
的批量顯示,有關(guān)帖子內(nèi)容
ForumMessage
也是采取類似做法。在每個(gè)
ForumThread
中可能有很多帖子內(nèi)容(
ForumMessage
對(duì)象集合),也不能在一個(gè)頁(yè)面中全部顯示,所以也是使用迭代模式來實(shí)現(xiàn)的。顯示一個(gè)
Forum
主題下所有帖子內(nèi)容的功能由
ForumThread
的
messages()
方法完成,檢查它的代理類
FroumThreadProxy
如何具體完成:
public Iterator messages(ResultFilter resultFilter) {
?? Iterator iterator = thread.messages(resultFilter);
?? return new IteratorProxy(JiveGlobals.MESSAGE, iterator, authorization, permissions);
}
實(shí)現(xiàn)的原理基本相同,返回的都是一個(gè)
Iterator
代理類,在這些代理類中都是進(jìn)行用戶權(quán)限檢驗(yàn)的。
Jive
中也有關(guān)于一次性獲得所有數(shù)據(jù),然后遍歷
ResultSet
的做法。這種做法主要適合一次性查詢數(shù)據(jù)庫(kù)的所有數(shù)據(jù),例如查詢當(dāng)前所有論壇
Forum
,首先實(shí)現(xiàn)
SQL
語(yǔ)句:
SELECT forumID FROM jiveForum
獲得所有
Forum
的
forumID
,這段代碼位于
DbForumFactory.java
的
forums
方法中,如
下:
? public Iterator forums() {
??? if (forums == null) {
????? LongList forumList = new LongList();
????? Connection con = null;
????? PreparedStatement pstmt = null;
????? try {
??????? con = ConnectionManager.getConnection();
??????? // GET_FORUMS
值是
SELECT forumID FROM jiveForum
??????? pstmt = con.prepareStatement(GET_FORUMS);
??????? ResultSet rs = pstmt.executeQuery();
??????? while (rs.next()) {
????????? forumList.add(rs.getLong(1));???????????????? //
將所有查詢
ID
結(jié)果放入
forumList
中
??????? }
????? }catch (SQLException sqle) {
??????? sqle.printStackTrace();
????? } finally {
??????? …
??? }
??? return new DatabaseObjectIterator(JiveGlobals.FORUM, forums, this);
? }
forums
方法是返回一個(gè)
DatabaseObjectIterator
,這個(gè)
DatabaseObjectIterator
也是一個(gè)迭代器,但是實(shí)現(xiàn)原理要比
ForumThreadBlockIterator
簡(jiǎn)單。它只提供了一個(gè)遍歷指針,在所有
ID
結(jié)果集中遍歷,然后也是通過
ID
獲得完整的數(shù)據(jù)對(duì)象。
總之,
Jive
中關(guān)于批量查詢有兩種實(shí)現(xiàn)方式:以
ForumThreadBlockIterator
為代表的實(shí)現(xiàn)方式適合在數(shù)據(jù)量巨大、需要多頁(yè)查詢時(shí)使用;而
DatabaseObjectIterator
則是推薦在一個(gè)頁(yè)面中顯示少量數(shù)據(jù)時(shí)使用。
2.6?
過濾器與裝飾模式
裝飾(
Decorator
)模式是動(dòng)態(tài)給一個(gè)對(duì)象添加一些額外的職責(zé),或者說改變這個(gè)對(duì)象的一些行為。這就類似于使用油漆為某個(gè)東西刷上油漆,在原來的對(duì)象表面增加了一層外衣。
在裝飾模式中,有兩個(gè)主要角色:一個(gè)是被刷油漆的對(duì)象(
decoratee
);另外一個(gè)是給
decoratee
刷油漆的對(duì)象(
decorator
)。這兩個(gè)對(duì)象都繼承同一個(gè)接口。
首先舉一個(gè)簡(jiǎn)單例子來說明什么是裝飾模式。
先創(chuàng)建一個(gè)接口:
public interface Work
{
public void insert();
}
這是一種打樁工作的抽象接口,動(dòng)作
insert
表示插入,那么插入什么?下面這個(gè)實(shí)現(xiàn)表示方形木樁的插入:
public class SquarePeg implements Work{
public void insert(){
System.out.println("
方形樁插入
");
}
}
本來這樣也許就可以滿足打樁的工作需要,但是有可能土質(zhì)很硬,在插入方形樁之前先要打一個(gè)洞,那么又將如何實(shí)現(xiàn)?可以編制一個(gè)
Decorator
類,同樣繼承
Work
接口,但是在實(shí)現(xiàn)
insert
方法時(shí)有些特別:
public class Decorator implements Work{
private Work work;
//
額外增加的功能被打包在這個(gè)
List
中
private ArrayList others = new ArrayList();
public Decorator(Work work)
{
this.work=work;
others.add("
打洞
");?? //
準(zhǔn)備好額外的功能
}
public void insert(){
otherMethod();
work.insert();
}
public void otherMethod()
{
ListIterator listIterator = others.listIterator();
while (listIterator.hasNext())
{
System.out.println(((String)(listIterator.next())) + "
正在進(jìn)行
");
}
}
}
在
Decorator
的方法
insert
中先執(zhí)行
otherMethod()
方法,然后才實(shí)現(xiàn)
SquarePeg
的
insert
方法。油漆工
Decorator
給被油漆者
SquarePeg
添加了新的行為
——
打洞。具體客戶端調(diào)用如下:
Work squarePeg
=
new SquarePeg();
Work decorator = new Decorator(squarePeg);
decorator.insert();
本例中只添加了一個(gè)新的行為(打洞),如果還有很多類似的行為,那么使用裝飾模式的優(yōu)點(diǎn)就體現(xiàn)出來了。因?yàn)榭梢酝ㄟ^另外一個(gè)角度(如組織新的油漆工實(shí)現(xiàn)子類)來對(duì)這些行為進(jìn)行混合和匹配,這樣就不必為每個(gè)行為創(chuàng)建一個(gè)類,從而減少了系統(tǒng)的復(fù)雜性。
使用裝飾模式可以避免在被油漆對(duì)象
decoratee
中包裝很多動(dòng)態(tài)的,可能需要也可能不需要的功能,只要在系統(tǒng)真正運(yùn)行時(shí),通過油漆工
decorator
來檢查那些需要加載的功能,實(shí)行動(dòng)態(tài)加載。
Jive
論壇實(shí)現(xiàn)了信息過濾功能。例如可以將帖子內(nèi)容中的
HTML
語(yǔ)句過濾掉;可以將帖子內(nèi)容中
Java
代碼以特別格式顯示等。這些過濾功能有很多,在實(shí)際使用時(shí)不一定都需要,是由實(shí)際情況選擇的。例如有的論壇就不需要將帖子內(nèi)容的
HTML
語(yǔ)句過濾掉,選擇哪些過濾功能是由論壇管理者具體動(dòng)態(tài)決定的。而且新的過濾功能可能隨時(shí)可以定制開發(fā)出來,如果試圖強(qiáng)行建立一種接口包含所有過濾行為,那么到時(shí)有新過濾功能加入時(shí),還需要改變接口代碼,真是一種危險(xiǎn)的行為。
裝飾模式可以解決這種運(yùn)行時(shí)需要?jiǎng)討B(tài)增加功能的問題,且看看
Jive
是如何實(shí)現(xiàn)的。
前面討論過,在
Jive
中,有主要幾個(gè)對(duì)象
ForumFactory
、
Forum
以及
ForumThread
和
ForumMessage
,它們之間的關(guān)系如圖
3-2
所示。因此帖子內(nèi)容
ForumMessage
對(duì)象的獲得是從其上級(jí)
FroumThread
的方法
getMessage
中獲取,但是在實(shí)際代碼中,
ForumThread
的方法
getMessage
委托
ForumFactory
來獲取
ForumMessage
對(duì)象。看看
ForumThread
的子類
DbForumThread
的
getMessage
代碼:
public ForumMessage getMessage(long messageID)
???????? throws ForumMessageNotFoundException
{
???? return factory.getMessage(messageID, this.id, forumID);
}
這是一種奇怪的委托,大概是因?yàn)樾枰紤]到過濾器功能有意為之吧。那就看看
ForumFactory
的具體實(shí)現(xiàn)子類
DbForumFactory
的
getMessage
功能,
getMessage
是將數(shù)據(jù)庫(kù)中的
ForumMessage
對(duì)象經(jīng)由過濾器過濾一遍后輸出(注:因?yàn)樵瓉淼?/span>
Jive
的
getMessage
代碼考慮到可緩存或不可緩存的過濾,比較復(fù)雜,實(shí)際過濾功能都是可以緩存的,因此精簡(jiǎn)如下)。
protected ForumMessage getMessage(long messageID, long threadID, long forumID)
??????????? throws ForumMessageNotFoundException
{
? ??????DbForumMessage message = cacheManager.messageCache.get(messageID);
??????? // Do a security check to make sure the message comes from the thread.
??????? if (message.threadID != threadID) {
??????????? throw new ForumMessageNotFoundException();
???? ???}
??????? ForumMessage filterMessage = null;
??????????? try {
? //
應(yīng)用全局過濾器
???? filterMessage = filterManager.applyFilters(message);
??????????????? Forum forum = getForum(forumID);???????????????
??????????????? //
應(yīng)用本論壇過濾器
??????????????? filterMessage = forum.getFilterManager().applyFilters(filterMessage);
??????????? }
??????????? catch (Exception e) { }
??????? return filterMessage;
}
上面代碼實(shí)際是裝飾模式的客戶端調(diào)用代碼,
DbForumMessage
的實(shí)例
message
是被油漆者
decoratee
。通過
filterManager
或
forum.getFilterManager()
的
applyFilter
方法,將
message
實(shí)行了所有的過濾功能。這就類似前面示例的下列語(yǔ)句:
Work decorator = new Decorator(squarePeg);
forum.getFilterManager()
是從數(shù)據(jù)庫(kù)中獲取當(dāng)前配置的所有過濾器類。每個(gè)
Forum
都有一套自己的過濾器類,這是通過下列語(yǔ)句實(shí)現(xiàn)的:
FilterManager filterManager =? new DbFilterManager();
在
DbFilterManager
的類變量
ForumMessageFilter [] filters
中保存著所有的過濾器,
applyFilters
方法實(shí)行過濾如下:
public ForumMessage applyFilters(ForumMessage message) {
??? for (int i=0; i < filters.length; i++) {
? ???????if (filters[i] != null) {
??????????? message = filters[i].clone(message);
???????? }
?? ??}
??? ?return message;
}
而
ForumMessageFilter
是
ForumMessage
的另外一個(gè)子類,被油漆者
DbForumMessage
通過油漆工
ForumMessageFilter
增加了一些新的行為和功能(過濾),如圖
3-6
所示。
圖
3-6?
裝飾模式
這就組成了一個(gè)稍微復(fù)雜一點(diǎn)的裝飾模式。
HTMLFilter
實(shí)現(xiàn)了
HTML
代碼過濾功能,而
JavaCodeHighLighter
實(shí)現(xiàn)了
Java
代碼過濾功能,
HTMLFilter
代碼如下:
public class HTMLFilter extends ForumMessageFilter {
??? public ForumMessageFilter clone(ForumMessage message){
??????? HTMLFilter filter = new HTMLFilter();
??????? filter.message = message;
??????? return filter;
??? }
??? public boolean isCacheable() {
??????? return true;
??? }
??? public String getSubject() {
??????? return StringUtils.escapeHTMLTags(message.getSubject());
??? }
??? public String getBody() {
??????? return StringUtils.escapeHTMLTags(message.getBody());
??? }
}
HTMLFilter
中重載了
ForumMessage
的
getSubject()
、
getBody()
方法,實(shí)際是改變了這兩個(gè)原來的行為,這類似前面舉例的方法:
public void insert(){
otherMethod();
work.insert();
}
這兩者都改變了被油漆者的行為。
在
HTMLFilter
中還使用了原型(
Prototype
)模式,原型模式定義是:
用原型實(shí)例指定創(chuàng)建對(duì)象的種類,并且通過復(fù)制這些原型創(chuàng)建新的對(duì)象。按照這種定義,
Java
的
clone
技術(shù)應(yīng)該是原型模式的一個(gè)實(shí)現(xiàn)。
HTMLFilter
的
clone
方法實(shí)際就是在當(dāng)前
HTMLFilter
實(shí)例中再生成一個(gè)同樣的實(shí)例。這樣在處理多個(gè)并發(fā)請(qǐng)求時(shí),不用通過同一個(gè)過濾器實(shí)例進(jìn)行處理,提高了性能。但是
HTMLFilter
的
clone
方法是采取
new
方法來實(shí)現(xiàn),不如直接使用
Object
的
native
方法速度快。
因?yàn)樵?/span>
DbFilterManager
中是根據(jù)配置使用類反射機(jī)制動(dòng)態(tài)分別生成包括
HTMLFilter
在內(nèi)的過濾器實(shí)例。但是每種過濾器實(shí)例只有一個(gè),為了使得大量用戶不必爭(zhēng)奪一個(gè)過濾器實(shí)例來實(shí)現(xiàn)過濾,就采取了克隆方式,這種實(shí)戰(zhàn)手法可以借鑒在自己的應(yīng)用系統(tǒng)中。
2.7?
主題監(jiān)測(cè)與觀察者模式
觀察者(
Observer
)模式是定義對(duì)象之間一對(duì)多的依賴關(guān)系,當(dāng)一個(gè)被觀察的對(duì)象發(fā)生改變時(shí),所有依賴于它的對(duì)象都會(huì)得到通知并采取相應(yīng)行為。
使用觀察者模式的優(yōu)點(diǎn)是將被觀察者和觀察者解耦,從而可以不影響被觀察者繼續(xù)自己的行為動(dòng)作。觀察者模式適合應(yīng)用于一些“事件觸發(fā)”場(chǎng)合。
在
Jive
中,
用戶也許會(huì)對(duì)某個(gè)主題感興趣,希望關(guān)于此主題發(fā)生的任何新的討論能通過電子郵件通知他,因此他訂閱監(jiān)視了這個(gè)主題。因?yàn)檫@個(gè)功能的實(shí)現(xiàn)會(huì)引入電子郵件的發(fā)
送。在前面章節(jié)已經(jīng)討論了電子郵件發(fā)送有可能因?yàn)榫W(wǎng)絡(luò)原因延遲,如果在有人回復(fù)這個(gè)主題時(shí),立即進(jìn)行電子郵件發(fā)送,通知所有訂閱該主題的用戶。那么該用戶
可能等待很長(zhǎng)時(shí)間得不到正常回應(yīng)。
使用觀察者模式,可以通過觸發(fā)一個(gè)觀察者,由觀察者通過另外線程來實(shí)施郵件發(fā)送,而被觀察者發(fā)出觸發(fā)通知后,可以繼續(xù)自己原來的邏輯行為。
看看
Jive
的
WatchManager
類:
public interface WatchManager {
??? //
正常監(jiān)察類型,用戶在這個(gè)主題更新后再次訪問時(shí),會(huì)明顯地發(fā)現(xiàn)
??? public static final int NORMAL_WATCH = 0;
??? ?//
當(dāng)主題變化時(shí),通過電子郵件通知用戶
??? public static final int EMAIL_NOTIFY_WATCH = 1;
??? //
設(shè)置一個(gè)主題被觀察的時(shí)間,默認(rèn)為
30
天
??? public void setDeleteDays(int deleteDays) throws UnauthorizedException;
??? public int getDeleteDays();
??? //
是否激活了
E-mail
提醒
? ??public boolean isEmailNotifyEnabled() throws UnauthorizedException;
??? public void setEmailNotifyEnabled(boolean enabled) throws UnauthorizedException;
??? //
保存
E-mail
的內(nèi)容
??? public String getEmailBody() throws UnauthorizedException;
??? public void setEmailBody(String body) throws UnauthorizedException;
??? //
保存
E-mail
的主題
??? public String getEmailSubject() throws UnauthorizedException;
public void setEmailSubject(String subject) throws UnauthorizedException;
??? …
?
??? //
為某個(gè)主題創(chuàng)建一個(gè)觀察者
??? public void createWatch(User user, ForumThread thread, int watchType)
??????????? throws UnauthorizedException;
??? //
刪除某個(gè)主題的觀察者
??? public void deleteWatch(User user, ForumThread thread, int watchType)
??? //
得到一個(gè)主題的所有觀察者
??? public Iterator getWatchedForumThreads(User user, int watchType)
??????????? throws UnauthorizedException;
??? //
判斷一個(gè)用戶是否在觀察監(jiān)視該主題
??? public boolean isWatchedThread(User user, ForumThread thread, int watchType)
??????????? throws UnauthorizedException;
??? …
}
DbWatchManager
是
WatchManager
的一個(gè)子類,通過數(shù)據(jù)庫(kù)保存著有關(guān)某個(gè)主題被哪些用戶監(jiān)視等數(shù)據(jù)資料。
WatchManager
對(duì)象是隨同
DbForumFactory()
一起生成的。
在
DbWatchManager
中有一個(gè)
WatchManager
沒有的很重要的方法
——
通知方法:
protected void notifyWatches(ForumThread thread) {
???? //If watches are turned on.
??? if (!emailNotifyEnabled) {
??????????? return;
???? }
???? //
通知所有觀察這個(gè)主題的用戶
???? EmailWatchUpdateTask task = new EmailWatchUpdateTask(this, factory, thread);
???? TaskEngine.addTask(task);
?}
這個(gè)方法用來觸發(fā)所有有關(guān)這個(gè)主題的監(jiān)視或訂閱用戶,以
E-mail
發(fā)送提醒他們。那么這個(gè)通知方法本身又是如何被觸發(fā)的?從功能上分析,應(yīng)該是在發(fā)表新帖子時(shí)觸發(fā)。
在
DbForumThread
的
addMessage
的最后一行有一句:
factory.watchManager.notifyWatches(this);
這其實(shí)是調(diào)用了
DbWatchManager
的
notifyWatches
方法,因此確實(shí)是在增加新帖子時(shí)觸發(fā)了該帖子的所有觀察者。
notifyWatches
方法中在執(zhí)行
E-mail
通知用戶時(shí),使用了
TaskEngine
來執(zhí)行
E-mail
發(fā)送。
E-mailWatchUpdateTask
是一個(gè)線程類,而
TaskEngine
是線程任務(wù)管理器,專門按要求啟動(dòng)如
E-mailWatchUpdateTask
這樣的任務(wù)線程。其實(shí)
TaskEngine
是一個(gè)簡(jiǎn)單的線程池,它不斷通過查詢
Queue
是否有可運(yùn)行的線程,如果有就直接運(yùn)行線程。
public class TaskEngine {
??? //
任務(wù)列表
??? private static LinkedList taskList = null;
??? //
工作數(shù)組
??? private static Thread[] workers = null;
??? private static Timer taskTimer = null;
??? private static Object lock = new Object();
?
??? static {
??????? //
根據(jù)配置文件初始化任務(wù)啟動(dòng)時(shí)間
??????? taskTimer = new Timer(true);
??????? //
默認(rèn)使用
7
個(gè)線程來裝載啟動(dòng)任務(wù)
??????? workers = new Thread[7];
??????? taskList = new LinkedList();
??????? for (int i=0; i<workers.length; i++) {
???????????? // TaskEngineWorker
是個(gè)簡(jiǎn)單的線程類
??????????? TaskEngineWorker worker = new TaskEngineWorker();
??????????? workers[i] = new Thread(worker);
??????????? workers[i].setDaemon(true);
??????????? workers[i].start();???????? //
啟動(dòng)
TaskEngineWorker
這個(gè)線程
??????? }
??? }
??? //TaskEngineWorker
內(nèi)部類
??? private static class TaskEngineWorker implements Runnable {
??????? private boolean done = false;
??????? public void run() {
??????????? while (!done) {
??????????????? //
運(yùn)行
nextTask
方法
??????????????? nextTask().run();
??????????? }
????? ??}
??? }
??? // nextTask()
返回的是一個(gè)可運(yùn)行線程,是任務(wù)列表
Queue
的一個(gè)讀取者
??? private static Runnable nextTask() {
??????? synchronized(lock) {
??????????? //
如果沒有任務(wù),就鎖定在這里
??????????? while (taskList.isEmpty()) {
??????????????? try {
??????????????????? lock.wait();????? ??//
等待解鎖
??????????????? } catch (InterruptedException ie) { }
??????????? }
??????????? //
從任務(wù)列表中取出第一個(gè)任務(wù)線程
??????????? return (Runnable)taskList.removeLast();
??????? }
??? }
??? public static void addTask(Runnable r) {
??????? addTask(r, Thread.NORM_PRIORITY);
??? }
??? //
這是任務(wù)列表
Queue
的生產(chǎn)者
??? public static void addTask(Runnable task, int priority) {
??????? synchronized(lock) {
??????????? taskList.addFirst(task);
??????????? //
提醒所有鎖在
lock
這里的線程可以運(yùn)行了
??????????? //
這是線程的互相通知機(jī)制,可參考線程參考資料
??????????? lock.notifyAll();
??????? }
??? }
??? …
}
在
TaskEngine
中啟動(dòng)設(shè)置了一個(gè)消息管道
Queue
和兩個(gè)線程。一個(gè)線程是負(fù)責(zé)向
Queue
里放入
Object
,可謂是消息的生產(chǎn)者;而另外一個(gè)線程負(fù)責(zé)從
Queue
中取出
Object
,如果
Queue
中沒有
Object
,那它就鎖定(
Block
)在那里,直到
Queue
中有
Object
,因?yàn)檫@些
Object
本身也是線程,因此它取出后就直接運(yùn)行它們。
這個(gè)
TaskEngine
建立的模型非常類似
JMS
(
Java
消息系統(tǒng)),雖然它們功能類似,但不同的是:
JMS
是一個(gè)分布式消息發(fā)布機(jī)制,可以在多臺(tái)服務(wù)器上運(yùn)行,處理能力要強(qiáng)大得多。而
TaskEngine
由于基于線程基礎(chǔ),因此不能跨
JVM
實(shí)現(xiàn)。可以說
TaskEngine
是一個(gè)微觀組件,而
JMS
則是一個(gè)宏觀架構(gòu)系統(tǒng)。
JMS
相關(guān)討論將在后面章節(jié)進(jìn)行。
以上討論了
Jive
系統(tǒng)中觀察者模式的實(shí)現(xiàn),
Jive
使用線程比較基礎(chǔ)的概念實(shí)現(xiàn)了觀察者模式,當(dāng)然有助于了解
J2EE
很多底層的基礎(chǔ)知識(shí),整個(gè)
Web
容器的技術(shù)實(shí)現(xiàn)就是基于線程池原理建立的。
Java
的
JDK
則提供了比較方便的觀察者模式
API——java.util.Observable
和
java.util.Observer
,它們的用戶非常簡(jiǎn)單,只要被觀察者繼承
Observable
,然后使用下列語(yǔ)句設(shè)置觀察點(diǎn):
setChanged();
notifyObservers(name); //
一旦執(zhí)行本代碼,就觸發(fā)觀察者了
而觀察者只要實(shí)現(xiàn)
Observer
接口,并實(shí)現(xiàn)
update
方法,在
update
方法中將被觀察者觸發(fā)后傳來的
object
進(jìn)行處理。舉例如下:
網(wǎng)上商店中商品價(jià)格可能發(fā)生變化,如果需要在價(jià)格變化時(shí),首頁(yè)能夠自動(dòng)顯示這些降價(jià)產(chǎn)品,那么使用觀察者模式將方便得多。首先,商品是一個(gè)被觀察者:
public class product extends Observable{
private float price;
public float getPrice(){ return price;}
public void setPrice(){
this.price=price;
//
商品價(jià)格發(fā)生變化,觸發(fā)觀察者
setChanged();
notifyObservers(new Float(price));
}
...
}
價(jià)格觀察者實(shí)現(xiàn)
observer
接口:
public class PriceObserver implements Observer{
private float price=0;
public void update(Observable obj,Object arg){
if (arg instanceof Float){
price=((Float)arg).floatValue();
System.out.println("PriceObserver :price changet to "+price);
}
}
}
這樣,一個(gè)簡(jiǎn)單的觀察者模式就很容易地實(shí)現(xiàn)了。