国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
Jive功能與設(shè)計模式

1  Jive功能需求

Jive功能需求分析類似于一個新系統(tǒng)的需求分析。只有了解Jive系統(tǒng)實現(xiàn)了哪些論壇功能,才能進一步研究和學(xué)習(xí)它是怎樣巧妙、優(yōu)雅地實現(xiàn)這些功能的。

論壇系統(tǒng)是網(wǎng)絡(luò)交流的一種主要互動功能系統(tǒng),如圖3-1所示。通過論壇系統(tǒng),用戶可以共同就某個話題不斷進行討論,通過發(fā)貼功能發(fā)布新的話題,通過回貼功能回復(fù)別人的話題。Jive論壇系統(tǒng)可以允許管理員動態(tài)地創(chuàng)建新的論壇、編輯論壇的內(nèi)容、設(shè)置論壇過濾信息以及管理注冊用戶等。

3-1  Jive用例圖

Jive論壇系統(tǒng)中,用戶角色和權(quán)限是緊密聯(lián)系在一起的。主要分兩大角色:普通用戶和管理員,具體的表現(xiàn)形式是通過權(quán)限組合來體現(xiàn)的。管理方面的權(quán)限有:

·          SYSTEM_ADMIN,系統(tǒng)管理員,可以管理整個系統(tǒng)。

·          FORUM_ADMIN,論壇管理員,可以管理某個特定的論壇。

·          USER_ADMINGROUP_ADMIN,用戶和組管理員,可以管理一些特定用戶和用戶組。

論壇的讀寫權(quán)限包括:讀權(quán)限,創(chuàng)建一個新主題,創(chuàng)建一個新的帖子等。

Jive中沒有明確定義普通用戶和管理員角色,而是直接通過以上權(quán)限組合和具體用戶直接建立聯(lián)系,并將這種直接聯(lián)系保存到數(shù)據(jù)庫中。

在權(quán)限不是很復(fù)雜的情況下,這種沒有引入角色的做法比較簡單直接。但由于用戶和權(quán)限直接掛鉤,而用戶和權(quán)限都可能在不斷地動態(tài)變化,那么它們之間由于聯(lián)系太直接和緊密,對各自變化形成了限制。所以,對于復(fù)雜的權(quán)限系統(tǒng),引入了基于角色的權(quán)限系統(tǒng),這將在以后章節(jié)中進一步討論。

Jive論壇業(yè)務(wù)對象主要分為Forum、ForumThreadForumMessage,它們之間的關(guān)系如圖3-2所示。

每個論壇Forum包含一系列ForumThread(主題),而每個主題都是由很多內(nèi)容帖子ForumMessage組成的,這是一個聚集關(guān)系。這3種對象中每一個對象都涉及到對象數(shù)據(jù)的創(chuàng)建、編輯、查詢和刪除,這些對象數(shù)據(jù)分別保存在數(shù)據(jù)庫中。這3個對象對于不同的角色可操作訪問權(quán)限是不一樣的,只有系統(tǒng)管理員和論壇管理員可以對Forum相關(guān)數(shù)據(jù)實行操作,普通用戶可以創(chuàng)建或編輯ForumThreadForumMessage。

Jive論壇為了實現(xiàn)不同用戶對不同基本對象的不同操作權(quán)限,通過設(shè)定一個統(tǒng)一的入口,在這個入口將檢查客戶端每次對數(shù)據(jù)的操作權(quán)限,如圖3-3所示。

  

3-2  基本對象關(guān)系圖                            3-3  入口示意圖

客戶端每次對數(shù)據(jù)庫的操作,都要經(jīng)過ForumFactory入口進入。在ForumFactory中會動態(tài)生成一個訪問控制代理ForumFactoryProxy,通過ForumFactoryProxy檢查客戶端訪問方法是否符合整體權(quán)限訪問控制要求。

下面將從ForumFactory作為Jive論壇系統(tǒng)分析入手,結(jié)合設(shè)計模式逐步分解論壇功能的具體實現(xiàn)。

2  Jive與設(shè)計模式

Jive論壇系統(tǒng)使用大量設(shè)計模式巧妙地實現(xiàn)了一系列功能。因為設(shè)計模式的通用性和可理解性,將幫助更多人很快地理解 Jive論壇源碼,從而可以依據(jù)一種“協(xié)定”來動態(tài)地擴展它。那么使用設(shè)計模式還有哪些好處?

2.1  設(shè)計模式

設(shè)計模式是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過分類編目的、代碼設(shè)計經(jīng)驗的總結(jié)。使用設(shè)計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。毫無疑問,設(shè)計模式于己于他人于系統(tǒng)都是多贏的。設(shè)計模式使代碼編制真正工程化,設(shè)計模式是軟件工程的基石。

GOF(設(shè)計模式作者簡稱)《設(shè)計模式》這本書第一次將設(shè)計模式提升到理論高度,并將之規(guī)范化,該書提出了23種基本設(shè)計模式。自此,在可復(fù)用面向?qū)ο筌浖陌l(fā)展過程中,新的大量的設(shè)計模式不斷出現(xiàn)。

很多人都知道Java是完全面向?qū)ο蟮脑O(shè)計和編程語言,但是由于接受教育以及經(jīng)驗的原因,大多數(shù)程序員或設(shè)計人員都是從傳統(tǒng)的過程語言轉(zhuǎn)變而來,因此在思維習(xí)慣上要完全轉(zhuǎn)變?yōu)槊嫦驅(qū)ο蟮脑O(shè)計和開發(fā)方式是困難的,而學(xué)習(xí)設(shè)計模式可以更好地幫助和堅固這種轉(zhuǎn)變。

凡是學(xué)習(xí)完成設(shè)計模式的人都有一種類似重生的感覺,這種重生可以從很多方面去解釋。換一種新的角度來看待和解決問題應(yīng)該是一種比較貼切的解釋,而這種新的思維角度培養(yǎng)屬于基礎(chǔ)培訓(xùn),因此,設(shè)計模式是學(xué)習(xí)Java的必讀基礎(chǔ)課程之一。

由于設(shè)計模式概念比較抽象,對于初學(xué)者學(xué)習(xí)有一定的難度,因此結(jié)合Jive論壇系統(tǒng)學(xué)習(xí)設(shè)計模式將是一種很好的選擇。

掌握了設(shè)計模式,將會幫助程序員或設(shè)計人員以更加可重用性、可伸縮性的眼光來開發(fā)應(yīng)用系統(tǒng),甚至開發(fā)通用的框架系統(tǒng)。框架系統(tǒng)是構(gòu)成一類特定軟件可復(fù)用設(shè)計的一組相互協(xié)作的類,主要是對應(yīng)用系統(tǒng)中反復(fù)重用部分的提煉,類似一種模板,這是一種結(jié)構(gòu)性的模板。

框架通常定義了應(yīng)用體系的整體結(jié)構(gòu)、類和對象的關(guān)系等設(shè)計參數(shù),以便于具體應(yīng)用實現(xiàn)者能集中精力于應(yīng)用本身的特定細節(jié)。框架強調(diào)設(shè)計復(fù)用,而設(shè)計模式最小的可重用單位,因此框架不可避免地會反復(fù)使用到設(shè)計模式。關(guān)于通用框架系統(tǒng)的設(shè)計開發(fā)將在以后章節(jié)中討論。

其實Jive論壇本身也形成了一個基于Web結(jié)構(gòu)的通用框架系統(tǒng),因為它很多設(shè)計思想是可以重用的,例如設(shè)定一個總體入口,通過入口檢查用戶的訪問控制權(quán)限,當(dāng)然還有其他各方面的功能實現(xiàn)方式都是值得在其他系統(tǒng)中借鑒的,也正因為它以模式的形式表現(xiàn)出來,這種可重用性和可借鑒性就更強。

2.2  ForumFactory與工廠模式

工廠模式是GOF設(shè)計模式的主要常用模式,它主要是為創(chuàng)建對象提供了一種接口,工廠模式主要是封裝了創(chuàng)建對象的細節(jié)過程,從而使得外界調(diào)用一個對象時,根本無需關(guān)心這個對象是如何產(chǎn)生的。

GOF設(shè)計模式中,工廠模式分為工廠方法模式和抽象工廠模式。兩者主要區(qū)別是,工廠方法是創(chuàng)建一種產(chǎn)品接口下的產(chǎn)品對象,而抽象工廠模式是創(chuàng)建多種產(chǎn)品接口下的產(chǎn)品對象,非常類似Builder生成器模式。在平時實踐中,使用較多的基本是工廠方法模式。

以類SampleOne為例,要創(chuàng)建SampleOne的對象實例:

SampleOne sampleOne = new SampleOne();

如果Sample類有幾個相近的類:SampleTwoSampleThree,那么創(chuàng)建它們的實例分別是:

SampleTwo sampleTwo = new SampleTwo();

SampleThree sampleThree = new SampleThree();

其實這3個類都有一些共同的特征,如網(wǎng)上商店中銷售書籍、玩具或者化妝品。雖然它們是不同的具體產(chǎn)品,但是它們有一個共同特征,可以抽象為“商品”。日常生活中很多東西都可以這樣高度抽象成一種接口形式。上面這3個類如果可以抽象為一個統(tǒng)一接口SampleIF,那么上面語句就可以成為:

SampleIF sampleOne = new SampleOne();

SampleIF sampleTwo = new SampleTwo();

SampleIF sampleThree = new SampleThree();

在實際情況中,有時并不需要同時生成3種對象,而是根據(jù)情況在3者之中選一個。在這種情況下,需要使用工廠方法來完成了,創(chuàng)建一個叫SampleFactory的抽象類:

public class SampleFactory{

   public abstract SampleIFcreator();

}

在這個抽象工廠類中有一個抽象方法creator,但是沒有具體實現(xiàn),而是延遲到它的子類中實現(xiàn),創(chuàng)建子類SampleFactoryImp

public class SampleFactoryImpextends SampleFactory{

   public SampleIF creator(){

    //根據(jù)其他因素綜合判斷返回具體產(chǎn)品

    //假設(shè)應(yīng)該返回SampleOne對象

       returnnew SampleOne();

}

}

SampleFactoryImp中根據(jù)具體情況來選擇返回SampleOne、SampleTwoSampleThree。所謂具體情況有很多種:上下文其他過程計算結(jié)果;直接根據(jù)配置文件中配置。

上述工廠方法模式中涉及到一個抽象產(chǎn)品接口Sample,如果還有其他完全不同的產(chǎn)品接口,如Product等,一個子類SampleFactoryImp只能實現(xiàn)一套系列產(chǎn)品方案的生產(chǎn),如果還需要另外一套系統(tǒng)產(chǎn)品方案,就可能需要另外一個子類SampleFactoryImpTwo來實現(xiàn)。這樣,多個產(chǎn)品系列、多個工廠方法就形成了抽象工廠模式。

前面已經(jīng)討論在Jive中設(shè)置了論壇統(tǒng)一入口,這個統(tǒng)一入口就是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節(jié)討論

    if(factory == null) {

      synchronized(initLock){

        if(factory == null) {

            ...//從配置文件中獲得當(dāng)前className

          try{

              //動態(tài)裝載類

              Classc = Class.forName(className);

              factory= (ForumFactory)c.newInstance();

          }

          catch(Exception e) {

              returnnull;

          }

        }

      }

    }

    //返回 proxy.用來限制授權(quán)對forum的訪問

    returnnew ForumFactoryProxy(authorization, factory,factory.getPermissions(authorization));

  }

  //創(chuàng)鍵產(chǎn)品接口Forum的具體對象實例

  publicabstract Forum createForum(String name, String description)

  throwsUnauthorizedException, ForumAlreadyExistsException;

   //創(chuàng)鍵產(chǎn)品接口ForumThread的具體對象實例

public abstract ForumThread createThread(ForumMessagerootMessage)

throws UnauthorizedException;

//創(chuàng)鍵產(chǎn)品接口ForumMessage的具體對象實例

 

 

    public abstractForumMessage createMessage();

  ....

}

ForumFactory中提供了很多抽象方法如createForum、createThreadcreateMessage()等,它們是創(chuàng)建各自產(chǎn)品接口下的具體對象,這3個接口就是前面分析的基本業(yè)務(wù)對象Forum、ForumThreadForumMessage,這些創(chuàng)建方法在ForumFactory中卻不立即執(zhí)行,而是推遲到ForumFactory子類中實現(xiàn)。

ForumFactory的子類實現(xiàn)是com.Yasna.forum.database.DbForumFactory,這是一種數(shù)據(jù)庫實現(xiàn)方式。即在DbForumFactory中分別實現(xiàn)了在數(shù)據(jù)庫中createForum、createThreadcreateMessage()3種方法,當(dāng)然也提供了動態(tài)擴展到另外一套系列產(chǎn)品的生產(chǎn)方案的可能。如果使用XML來實現(xiàn),那么可以編制一個XmlForumFactory的具體工廠子類來分別實現(xiàn)3種創(chuàng)建方法。

因此,Jive論壇在統(tǒng)一入口處使用了抽象工廠模式來動態(tài)地創(chuàng)建論壇中所需要的各種產(chǎn)品,如圖3-4所示。

3-4  ForumFactory抽象工廠模式圖

3-4,XmlForumFactoryDbForumFactory作為抽象工廠ForumFactory的兩個具體實現(xiàn),Forum、ForumThreadForumMessage分別作為3個系列抽象產(chǎn)品接口依靠不同的工廠實現(xiàn)方式,會產(chǎn)生不同的產(chǎn)品對象。

從抽象工廠模式去理解Jive論壇統(tǒng)一入口處,可以一步到位掌握了幾個類之間的大概關(guān)系。因為使用了抽象工廠模式這種通用的設(shè)計模式,可以方便源碼閱讀者快速地掌握整個系統(tǒng)的結(jié)構(gòu)和來龍去脈,圖3-4這張圖已經(jīng)初步展示了Jive的主要框架結(jié)構(gòu)。

細心的讀者也許會發(fā)現(xiàn),在上面ForumFactory有一個getInstance比較令人費解,這將在2.3節(jié)進行討論。


2.3  統(tǒng)一入口與單態(tài)模式

在上面ForumFactorygetInstance方法使用單態(tài)(SingleTon)模式。單態(tài)模式是保證一個類有且僅有一個對象實例,并提供一個訪問它的全局訪問點。

前面曾提到ForumFactoryJive提供客戶端訪問數(shù)據(jù)庫系統(tǒng)的統(tǒng)一入口。為了保證所有的客戶端請求都要經(jīng)過這個ForumFactory,如果不使用單態(tài)模式,客戶端下列調(diào)用語句表示生成了ForumFactory實例:

ForumFactory factory = new DbForumFactory();

客戶端每發(fā)生一次請求都調(diào)用這條語句,這就會發(fā)生每次都生成不同factory對象實例,這顯然不符合設(shè)計要求,因此必須使用單態(tài)模式。

一般在Java實現(xiàn)單態(tài)模式有幾種選擇,最常用而且安全的用法如下:

public class Singleton {

  privateSingleton(){}

  //在自己內(nèi)部定義自己一個實例,是不是很奇怪

  //注意這是private,只供內(nèi)部調(diào)用

  privatestatic Singleton instance = new Singleton();

  //這里提供了一個供外部訪問本class的靜態(tài)方法,可以直接訪問

  publicstatic Singleton getInstance() {

    returninstance;

  }

}

單態(tài)模式一共使用了兩條語句實現(xiàn):第一條直接生成自己的對象,第二條提供一個方法供外部調(diào)用這個對象,同時最好將構(gòu)造函數(shù)設(shè)置為private,以防止其他程序員直接使用new Singleton生成實例。

還有一種Java單態(tài)模式實現(xiàn):

public class Singleton {

  privateSingleton(){}

  privatestatic Singleton instance = null;

  publicstatic synchronized Singleton getInstance() {

    if(instance==null)

      instancenew Singleton()

    returninstance;

  }

在上面代碼中,使用了判斷語句。如果instance為空,再進行實例化,這成為lazy initialization。注意getInstance()方法的synchronized,這個synchronized很重要。如果沒有synchronized,那么使用getInstance()在第一次被訪問時有可能得到多個Singleton實例。

關(guān)于lazyinitializationSingleton有很多涉及double-checked locking (DCL)的討論,有興趣者可以進一步研究。一般認為第一種形式要更加安全些;但是后者可以用在類初始化時需要參數(shù)輸入的情況下。

JiveForumFactory中采取了后者lazy initialization形式,這是為了能夠動態(tài)配置指定ForumFactory的具體子類。在getInstance中,從配置文件中獲得當(dāng)前工廠的具體實現(xiàn),如果需要啟動XmlForumFactory,就不必修改ForumFactory代碼,直接在配置文件中指定className的名字為XmlForumFactory。這樣通過下列動態(tài)裝載機制生成ForumFactory具體對象:

Class c = Class.forName(className);

factory = (ForumFactory)c.newInstance();

這是利用Java的反射機制,可以通過動態(tài)指定className的數(shù)值而達到生成對象的方式。

使用單態(tài)模式的目標是為了控制對象的創(chuàng)建,單態(tài)模式經(jīng)常使用在控制資源的訪問上。例如數(shù)據(jù)庫連接或Socket連接等。單態(tài)模式可以控制在某個時刻只有一個線程訪問資源。由于Java中沒有全局變量的概念,因此使用單態(tài)模式有時可以起到這種作用,當(dāng)然需要注意是在一個JVM中。

2.4  訪問控制與代理模式

仔細研究會發(fā)現(xiàn),在ForumFactorygetInstance方法中最后的返回值有些奇怪。按照單態(tài)模式的概念應(yīng)該直接返回factory這個對象實例,但是卻返回了ForumFactoryProxy的一個實例,這實際上改變了單態(tài)模式的初衷。這樣客戶端每次通過調(diào)用ForumFactorygetInstance返回的就不是ForumFactory的惟一實例,而是新的對象。之所以這樣做是為了訪問權(quán)限的控制,姑且不論這樣做的優(yōu)劣,先看看什么是代理模式。

代理模式是屬于設(shè)計模式結(jié)構(gòu)型模式中一種,它是實際訪問對象的代理對象,或者影子對象,主要達到控制實際對象的訪問。這種控制的目的很多,例如提高性能等。即遠程代理模式,這種模式將在以后章節(jié)討論。

其中一個主要的控制目的是控制客戶端對實際對象的訪問權(quán)限。在Jive系統(tǒng)中,因為有角色權(quán)限的分別,對于ForumForumThreadFroumMessage的訪問操作必須經(jīng)過權(quán)限機制驗證后才能進行。

ForumFactoryProxy中的createForum方法為例,其實ForumFactoryProxy也是FroumFactory的一種工廠實現(xiàn),它的createForum具體實現(xiàn)如下:

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();

        }

}

在這個方法中進行了權(quán)限驗證,判斷是否屬于系統(tǒng)管理員。如果是,將直接從DbForumFactory對象factory的方法createForum中獲得一個新的Forum對象,然后再返回Forum的子類代理對象ForumProxy。因為在Forum中也還有很多屬性和操作方法,這些也需要進行權(quán)限驗證。ForumProxyForumFactoryProxy起到類似的作用。

Jive中有下列幾個代理類:

·          ForumFactoryProxy:客戶端和DbForumFactory之間的代理??蛻舳嗽L問DbForumFactory的任何方法都要先經(jīng)過ForumFactoryProxy相應(yīng)方法代理一次。以下意思相同。

·          ForumProxy:客戶端和DbForum之間的代理,研究Forum對象的每個方法,必須先看ForumProxy對象的方法。

·          ForumMessageProxy:客戶端和DbForumMessage之間的代理。

·          ForumThreadProxy:客戶端和DbForumThread之間的代理。

UserGroup也有相應(yīng)的代理類。

由以上分析看出,每個數(shù)據(jù)對象都有一個代理。如果系統(tǒng)中數(shù)據(jù)對象非常多,依據(jù)這種一對一的代理關(guān)系,會有很多代理類,將使系統(tǒng)變得不是非常干凈,因此可以使用動態(tài)代理來代替這所有的代理類,具體實現(xiàn)將在以后章節(jié)討論。

2.5  批量分頁查詢與迭代模式

迭代(Iterator)模式是提供一種順序訪問某個集合各個元素的方法,確保不暴露該集合的內(nèi)部表現(xiàn)。迭代模式應(yīng)用于對大量數(shù)據(jù)的訪問,Java Collection APIIterator就是迭代模式的一種實現(xiàn)。

在前面章節(jié)已經(jīng)討論過,用戶查詢大量數(shù)據(jù),從數(shù)據(jù)庫不應(yīng)該直接返回ResultSet,應(yīng)該是Collection。但是有一個問題,如果這個數(shù)據(jù)很大,需要分頁面顯示。如果一下子將所有頁面要顯示的數(shù)據(jù)都查詢出來放在Collection,會影響性能。而使用迭代模式則不必將全部集合都展現(xiàn)出來,只有遍歷到某個元素時才會查詢數(shù)據(jù)庫獲得這個元素的數(shù)據(jù)。

以論壇中顯示帖子主題為例,在一個頁面中不可能顯示所有主題,只有分頁面顯示,如圖3-5所示。

3-5中一共分15頁來顯示所有論壇帖子,可以從顯示Forum.jsp中發(fā)現(xiàn)下列語句可以完成上述結(jié)果:

ResultFilterfilter = new ResultFilter();  //設(shè)置結(jié)果過濾器

   filter.setStartIndex(start);             //設(shè)置開始點

   filter.setNumResults(range);          //設(shè)置范圍

   ForumThreadIteratorthreads = forum.threads(filter);  //獲得迭代器

   while(threads.hasNext){

       //逐個顯示threads中帖子主題,輸出圖3-5中的每一行

   }

3-5  分頁顯示所有帖子

上述代碼中主要是從Forumthreads方法獲得迭代器ForumThreadIterator的實例,依據(jù)前面代理模式中分析、研究Forum對象的方法,首先是看ForumProxy中對應(yīng)方法,然后再看DbForum中對應(yīng)方法的具體實現(xiàn)。在ForumProxy,threads方法如下

public ForumThreadIterator threads(ResultFilterresultFilter) {

     ForumThreadIteratoriterator = forum.threads(resultFilter);

      returnnew ForumThreadIteratorProxy(iterator, authorization, permissions);

}

首先是調(diào)用了DbForum中具體的threads方法,再追蹤到DbForum中看看,它的threads方法代碼如下:

public ForumThreadIterator threads(ResultFilterresultFilter) {

//resultFilter設(shè)置范圍要求獲得SQL查詢語句

   String query = getThreadListSQL(resultFilter,false); 

   //獲得resultFilter設(shè)置范圍內(nèi)的所有ThreadID集合

   long [] threadBlock= getThreadBlock(query.toString(), resultFilter.getStartIndex());

   //以下是計算查詢區(qū)域的開始點和終點

   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是一個查詢結(jié)果類,可以對論壇主題Thread和帖子內(nèi)容Message進行過濾或排序,這樣就可以根據(jù)用戶要求定制特殊的查詢范圍。如查詢某個用戶去年在這個論壇發(fā)表的所有帖子,那只要創(chuàng)建一個ResultFilter對象就可以代表這個查詢要求。

在上面threads方法代碼中,第一步是先定制出相應(yīng)的動態(tài)SQL查詢語句,然后使用這個查詢語句查詢數(shù)據(jù)庫,獲得查詢范圍內(nèi)所有的ForumThreadID集合,然后在這個ID集合中獲得當(dāng)前頁面的ID子集合,這是非常關(guān)鍵的一步。

在這關(guān)鍵的一步中,有兩個重要的方法getThreadListSQLgetThreadBlock

·          GetThreadListSQL:獲得SQL查詢語句query的值,這個方法Jive實現(xiàn)起來顯得非常地瑣碎。

·          GetThreadBlock:獲得當(dāng)前頁面的ID子集合,那么如何確定ID子集合的開始位置呢?查看getThreadBlock方法代碼,可以發(fā)現(xiàn),它是使用最普遍的ResultSet next()方法來逐個跳躍到開始位置。

上面代碼的Threads方法中最后返回的是ForumThreadBlockIterator,它是抽象類ForumThreadIterator的子類,而ForumThreadIterator繼承了CollectionIterator,以此聲明自己是一個迭代器,ForumMessageBlockIterator實現(xiàn)的具體方法如下:

public boolean hasNext();     //判斷是否有下一個元素

public boolean hasPrevious()  //判斷是否有前一個元素

public Object next() throws java.util.NoSuchElementException  //獲得下一個元素實例

ForumThreadBlockIterator中的Block是“頁”的意思,它的一個主要類變量threadBlock包含的是一個頁面中所有ForumThreadIDnext()方法實際是對threadBlockForumThread進行遍歷,如果這個頁面全部遍歷完成,將再獲取下一頁(Block)數(shù)據(jù)。

ForumThreadBlockIterator重要方法getElement中實現(xiàn)了兩個功能:

·          如果當(dāng)前遍歷指針超過當(dāng)前頁面,將使用getThreadBlock獲得下一個頁面的ID子集合;

·          如果當(dāng)前遍歷指針在當(dāng)前頁面之內(nèi),根據(jù)ID獲得完整的數(shù)據(jù)對象,實現(xiàn)輸出;

ForumThreadBlockIteratorgetElement方法代碼如下:

private Object getElement(intindex) {

   if (index < 0){        return null;        }

   // 檢查所要獲得的 element 是否在本查詢范圍內(nèi)(當(dāng)前頁面內(nèi))

   if (index < blockStart||

index >= blockStart + DbForum.THREAD_BLOCK_SIZE){  

      try{

          //從緩沖中獲得Forum實例

          DbForumforum = factory.cacheManager.forumCache.get(forumID);

          //獲得下一頁的內(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) {

               returnnull;

       }

     }

     Objectelement = null;

     // 計算這個元素在當(dāng)前查詢范圍內(nèi)的相對位置

     int relativeIndex= index % DbForum.THREAD_BLOCK_SIZE;

     // Makesure index isn't too large

     if (relativeIndex < threadBlock.length){

        try{

            // 從緩沖中獲得實際thread 對象

            element= factory.cacheManager.threadCache.get(

                        threadBlock[relativeIndex]);

        }catch (ForumThreadNotFoundException tnfe) { }

     }

     returnelement;

}

ForumThreadBlockIterator是真正實現(xiàn)分頁查詢的核心功能,ForumThreadBlockIterator對象返回到客戶端的過程中,遭遇ForumThreadIteratorProxy的截獲,可以回頭看看ForumProxy中的threads方法,它最終返回給調(diào)用客戶端Forum.jsp的是ForumThreadIteratorProxy實例。

ForumThreadIteratorProxy也是迭代器ForumThreadIterator的一個子類,它的一個具體方法中:

public Object next() {

  return new ForumThreadProxy((ForumThread)iterator.next(),authorization,

            permissions);

}

這一句是返回一個ForumThreadProxy實例,返回就是一個ForumThread實例的代理。這里,Jive使用代理模式實現(xiàn)訪問控制實現(xiàn)得不是很巧妙,似乎有代理到處“飛”的感覺,這是可以對之進行改造的。

從以上可以看出,Jive在輸出如圖3-5所示的多頁查詢結(jié)果時,采取了下列步驟:

1)先查詢出符合查詢條件的所有對象元素的ID集合,注意不是所有對象元素,只是其ID的集合,這樣節(jié)約了大量內(nèi)存。

2)每個頁面視為一個Block,每當(dāng)進入下一頁時,獲得下一個頁面的所有對象的ID集合。

3)輸出當(dāng)前頁面的所有對象時,首先從緩沖中獲取,如果緩沖中沒有,再根據(jù)ID從數(shù)據(jù)庫中獲取完整的對象數(shù)據(jù)。

上述實現(xiàn)方法完全基于即查即顯,相比于一般批量查詢做法:一次性獲得所有數(shù)據(jù),然后遍歷數(shù)據(jù)結(jié)果集ResultSet,Jive這種批量查詢方式是一種比較理想的選擇。

以上是ForumThread的批量顯示,有關(guān)帖子內(nèi)容ForumMessage也是采取類似做法。在每個ForumThread中可能有很多帖子內(nèi)容(ForumMessage對象集合),也不能在一個頁面中全部顯示,所以也是使用迭代模式來實現(xiàn)的。顯示一個Forum主題下所有帖子內(nèi)容的功能由ForumThreadmessages()方法完成,檢查它的代理類FroumThreadProxy如何具體完成:

public Iterator messages(ResultFilterresultFilter) {

   Iterator iterator= thread.messages(resultFilter);

   return new IteratorProxy(JiveGlobals.MESSAGE,iterator, authorization, permissions);

}

實現(xiàn)的原理基本相同,返回的都是一個Iterator代理類,在這些代理類中都是進行用戶權(quán)限檢驗的。

Jive中也有關(guān)于一次性獲得所有數(shù)據(jù),然后遍歷ResultSet的做法。這種做法主要適合一次性查詢數(shù)據(jù)庫的所有數(shù)據(jù),例如查詢當(dāng)前所有論壇Forum,首先實現(xiàn)SQL語句:

SELECT forumID FROM jiveForum

獲得所有ForumforumID,這段代碼位于DbForumFactory.javaforums方法中,如下:

  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));                 //將所有查詢ID結(jié)果放入forumList

        }

      }catch(SQLException sqle) {

        sqle.printStackTrace();

      }finally {

        …

    }

    return new DatabaseObjectIterator(JiveGlobals.FORUM,forums, this);

  }

forums方法是返回一個DatabaseObjectIterator,這個DatabaseObjectIterator也是一個迭代器,但是實現(xiàn)原理要比ForumThreadBlockIterator簡單。它只提供了一個遍歷指針,在所有ID結(jié)果集中遍歷,然后也是通過ID獲得完整的數(shù)據(jù)對象。

總之,Jive中關(guān)于批量查詢有兩種實現(xiàn)方式:以ForumThreadBlockIterator為代表的實現(xiàn)方式適合在數(shù)據(jù)量巨大、需要多頁查詢時使用;而DatabaseObjectIterator則是推薦在一個頁面中顯示少量數(shù)據(jù)時使用。

2.6  過濾器與裝飾模式

裝飾(Decorator)模式是動態(tài)給一個對象添加一些額外的職責(zé),或者說改變這個對象的一些行為。這就類似于使用油漆為某個東西刷上油漆,在原來的對象表面增加了一層外衣。

在裝飾模式中,有兩個主要角色:一個是被刷油漆的對象(decoratee);另外一個是給decoratee刷油漆的對象(decorator)。這兩個對象都繼承同一個接口。

首先舉一個簡單例子來說明什么是裝飾模式。

先創(chuàng)建一個接口:

public interface Work

{

  publicvoid insert();

}

這是一種打樁工作的抽象接口,動作insert表示插入,那么插入什么?下面這個實現(xiàn)表示方形木樁的插入:

public class SquarePeg implementsWork{

  publicvoid insert(){

    System.out.println("方形樁插入");

  }

}

本來這樣也許就可以滿足打樁的工作需要,但是有可能土質(zhì)很硬,在插入方形樁之前先要打一個洞,那么又將如何實現(xiàn)?可以編制一個Decorator類,同樣繼承Work接口,但是在實現(xiàn)insert方法時有些特別:

public class Decorator implementsWork{

  privateWork work;

  //額外增加的功能被打包在這個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()))+ " 正在進行");

    }

}

}

Decorator的方法insert中先執(zhí)行otherMethod()方法,然后才實現(xiàn)SquarePeginsert方法。油漆工Decorator給被油漆者SquarePeg添加了新的行為——打洞。具體客戶端調(diào)用如下:

Work squarePeg new SquarePeg();

Work decorator = new Decorator(squarePeg);

decorator.insert();

本例中只添加了一個新的行為(打洞),如果還有很多類似的行為,那么使用裝飾模式的優(yōu)點就體現(xiàn)出來了。因為可以通過另外一個角度(如組織新的油漆工實現(xiàn)子類)來對這些行為進行混合和匹配,這樣就不必為每個行為創(chuàng)建一個類,從而減少了系統(tǒng)的復(fù)雜性。

使用裝飾模式可以避免在被油漆對象decoratee中包裝很多動態(tài)的,可能需要也可能不需要的功能,只要在系統(tǒng)真正運行時,通過油漆工decorator來檢查那些需要加載的功能,實行動態(tài)加載。

Jive論壇實現(xiàn)了信息過濾功能。例如可以將帖子內(nèi)容中的HTML語句過濾掉;可以將帖子內(nèi)容中Java代碼以特別格式顯示等。這些過濾功能有很多,在實際使用時不一定都需要,是由實際情況選擇的。例如有的論壇就不需要將帖子內(nèi)容的HTML語句過濾掉,選擇哪些過濾功能是由論壇管理者具體動態(tài)決定的。而且新的過濾功能可能隨時可以定制開發(fā)出來,如果試圖強行建立一種接口包含所有過濾行為,那么到時有新過濾功能加入時,還需要改變接口代碼,真是一種危險的行為。

裝飾模式可以解決這種運行時需要動態(tài)增加功能的問題,且看看Jive是如何實現(xiàn)的。

前面討論過,在Jive中,有主要幾個對象ForumFactory、Forum以及ForumThreadForumMessage,它們之間的關(guān)系如圖3-2所示。因此帖子內(nèi)容ForumMessage對象的獲得是從其上級FroumThread的方法getMessage中獲取,但是在實際代碼中,ForumThread的方法getMessage委托ForumFactory來獲取ForumMessage對象??纯?/span>ForumThread的子類DbForumThreadgetMessage代碼:

public ForumMessage getMessage(longmessageID)

         throwsForumMessageNotFoundException

{

     returnfactory.getMessage(messageID, this.id, forumID);

}

這是一種奇怪的委托,大概是因為需要考慮到過濾器功能有意為之吧。那就看看ForumFactory的具體實現(xiàn)子類DbForumFactorygetMessage功能,getMessage是將數(shù)據(jù)庫中的ForumMessage對象經(jīng)由過濾器過濾一遍后輸出(注:因為原來的JivegetMessage代碼考慮到可緩存或不可緩存的過濾,比較復(fù)雜,實際過濾功能都是可以緩存的,因此精簡如下)。

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{

  // 應(yīng)用全局過濾器

     filterMessage= filterManager.applyFilters(message);

                Forumforum = getForum(forumID);               

                //應(yīng)用本論壇過濾器

                filterMessage= forum.getFilterManager().applyFilters(filterMessage);

            }

            catch(Exception e) { }

        returnfilterMessage;

}

上面代碼實際是裝飾模式的客戶端調(diào)用代碼,DbForumMessage 的實例message是被油漆者decoratee。通過filterManager forum.getFilterManager()applyFilter方法,將message實行了所有的過濾功能。這就類似前面示例的下列語句:

Work decorator = new Decorator(squarePeg);

forum.getFilterManager()是從數(shù)據(jù)庫中獲取當(dāng)前配置的所有過濾器類。每個Forum都有一套自己的過濾器類,這是通過下列語句實現(xiàn)的:

FilterManager filterManager =  new DbFilterManager();

DbFilterManager 的類變量ForumMessageFilter[] filters中保存著所有的過濾器,applyFilters方法實行過濾如下:

public ForumMessage applyFilters(ForumMessagemessage) {

    for (int i=0;i < filters.length; i++) {

         if(filters[i] != null) {

            message= filters[i].clone(message);

         }

     }

     returnmessage;

}

ForumMessageFilterForumMessage的另外一個子類,被油漆者DbForumMessage通過油漆工ForumMessageFilter增加了一些新的行為和功能(過濾),如圖3-6所示。

3-6  裝飾模式

這就組成了一個稍微復(fù)雜一點的裝飾模式。HTMLFilter實現(xiàn)了HTML代碼過濾功能,而JavaCodeHighLighter實現(xiàn)了Java代碼過濾功能,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中重載了ForumMessagegetSubject()、getBody()方法,實際是改變了這兩個原來的行為,這類似前面舉例的方法:

public void insert(){

    otherMethod();

    work.insert();

}

這兩者都改變了被油漆者的行為。

HTMLFilter中還使用了原型(Prototype)模式,原型模式定義是:用原型實例指定創(chuàng)建對象的種類,并且通過復(fù)制這些原型創(chuàng)建新的對象。按照這種定義,Javaclone技術(shù)應(yīng)該是原型模式的一個實現(xiàn)。

HTMLFilterclone方法實際就是在當(dāng)前HTMLFilter實例中再生成一個同樣的實例。這樣在處理多個并發(fā)請求時,不用通過同一個過濾器實例進行處理,提高了性能。但是HTMLFilterclone方法是采取new方法來實現(xiàn),不如直接使用Objectnative方法速度快。

因為在DbFilterManager中是根據(jù)配置使用類反射機制動態(tài)分別生成包括HTMLFilter在內(nèi)的過濾器實例。但是每種過濾器實例只有一個,為了使得大量用戶不必爭奪一個過濾器實例來實現(xiàn)過濾,就采取了克隆方式,這種實戰(zhàn)手法可以借鑒在自己的應(yīng)用系統(tǒng)中。

2.7  主題監(jiān)測與觀察者模式

觀察者(Observer)模式是定義對象之間一對多的依賴關(guān)系,當(dāng)一個被觀察的對象發(fā)生改變時,所有依賴于它的對象都會得到通知并采取相應(yīng)行為。

使用觀察者模式的優(yōu)點是將被觀察者和觀察者解耦,從而可以不影響被觀察者繼續(xù)自己的行為動作。觀察者模式適合應(yīng)用于一些“事件觸發(fā)”場合。

Jive中,用戶也許會對某個主題感興趣,希望關(guān)于此主題發(fā)生的任何新的討論能通過電子郵件通知他,因此他訂閱監(jiān)視了這個主題。因為這個功能的實現(xiàn)會引入電子郵件的發(fā)送。在前面章節(jié)已經(jīng)討論了電子郵件發(fā)送有可能因為網(wǎng)絡(luò)原因延遲,如果在有人回復(fù)這個主題時,立即進行電子郵件發(fā)送,通知所有訂閱該主題的用戶。那么該用戶可能等待很長時間得不到正?;貞?yīng)。

使用觀察者模式,可以通過觸發(fā)一個觀察者,由觀察者通過另外線程來實施郵件發(fā)送,而被觀察者發(fā)出觸發(fā)通知后,可以繼續(xù)自己原來的邏輯行為。

看看JiveWatchManager類:

public interface WatchManager{

    //正常監(jiān)察類型,用戶在這個主題更新后再次訪問時,會明顯地發(fā)現(xiàn)

    public staticfinal int NORMAL_WATCH = 0;

     // 當(dāng)主題變化時,通過電子郵件通知用戶

    public staticfinal int EMAIL_NOTIFY_WATCH = 1;

    //設(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的內(nèi)容

    public StringgetEmailBody() throws UnauthorizedException;

    public voidsetEmailBody(String body) throws UnauthorizedException;

    //保存E-mail的主題

    public StringgetEmailSubject() throws UnauthorizedException;

public void setEmailSubject(Stringsubject) throws UnauthorizedException;

    …

 

    //為某個主題創(chuàng)建一個觀察者

    public voidcreateWatch(User user, ForumThread thread, int watchType)

            throwsUnauthorizedException;

    //刪除某個主題的觀察者

    public voiddeleteWatch(User user, ForumThread thread, int watchType)

    //得到一個主題的所有觀察者

    public IteratorgetWatchedForumThreads(User user, int watchType)

            throwsUnauthorizedException;

    //判斷一個用戶是否在觀察監(jiān)視該主題

    public booleanisWatchedThread(User user, ForumThread thread, int watchType)

            throwsUnauthorizedException;

    …

}

DbWatchManagerWatchManager的一個子類,通過數(shù)據(jù)庫保存著有關(guān)某個主題被哪些用戶監(jiān)視等數(shù)據(jù)資料。WatchManager對象是隨同DbForumFactory()一起生成的。

DbWatchManager中有一個WatchManager沒有的很重要的方法——通知方法:

protected void notifyWatches(ForumThreadthread) {

     //If watchesare turned on.

    if (!emailNotifyEnabled){

            return;

     }

     //通知所有觀察這個主題的用戶

     EmailWatchUpdateTasktask = new EmailWatchUpdateTask(this, factory, thread);

     TaskEngine.addTask(task);

 }

這個方法用來觸發(fā)所有有關(guān)這個主題的監(jiān)視或訂閱用戶,以E-mail發(fā)送提醒他們。那么這個通知方法本身又是如何被觸發(fā)的?從功能上分析,應(yīng)該是在發(fā)表新帖子時觸發(fā)。

DbForumThreadaddMessage的最后一行有一句:

factory.watchManager.notifyWatches(this);

這其實是調(diào)用了DbWatchManagernotifyWatches方法,因此確實是在增加新帖子時觸發(fā)了該帖子的所有觀察者。

notifyWatches方法中在執(zhí)行E-mail通知用戶時,使用了TaskEngine來執(zhí)行E-mail發(fā)送。E-mailWatchUpdateTask是一個線程類,而TaskEngine是線程任務(wù)管理器,專門按要求啟動如E-mailWatchUpdateTask這樣的任務(wù)線程。其實TaskEngine是一個簡單的線程池,它不斷通過查詢Queue是否有可運行的線程,如果有就直接運行線程。

public class TaskEngine {

    //任務(wù)列表

    private staticLinkedList taskList = null;

    //工作數(shù)組

    private staticThread[] workers = null;

    private staticTimer taskTimer = null;

    private staticObject lock = new Object();

 

    static {

        //根據(jù)配置文件初始化任務(wù)啟動時間

        taskTimer= new Timer(true);

        // 默認使用7個線程來裝載啟動任務(wù)

        workers= new Thread[7];

        taskList= new LinkedList();

        for(int i=0; i<workers.length; i++) {

             //TaskEngineWorker是個簡單的線程類

            TaskEngineWorkerworker = new TaskEngineWorker();

            workers[i]= new Thread(worker);

            workers[i].setDaemon(true);

            workers[i].start();         //啟動TaskEngineWorker這個線程

        }

    }

    //TaskEngineWorker內(nèi)部類

    private staticclass TaskEngineWorker implements Runnable {

        privateboolean done = false;

        publicvoid run() {

            while(!done) {

                //運行nextTask方法

                nextTask().run();

            }

        }

    }

    // nextTask()返回的是一個可運行線程,是任務(wù)列表Queue的一個讀取者

    private staticRunnable nextTask() {

        synchronized(lock){

            // 如果沒有任務(wù),就鎖定在這里

            while(taskList.isEmpty()) {

                try{

                    lock.wait();        //等待解鎖

                }catch (InterruptedException ie) { }

            }

            //從任務(wù)列表中取出第一個任務(wù)線程

            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這里的線程可以運行了

            //這是線程的互相通知機制,可參考線程參考資料

            lock.notifyAll();

        }

    }

    …

}

TaskEngine中啟動設(shè)置了一個消息管道Queue和兩個線程。一個線程是負責(zé)向Queue里放入Object,可謂是消息的生產(chǎn)者;而另外一個線程負責(zé)從Queue中取出Object,如果Queue中沒有Object,那它就鎖定(Block)在那里,直到Queue中有Object,因為這些Object本身也是線程,因此它取出后就直接運行它們。

這個TaskEngine建立的模型非常類似JMSJava消息系統(tǒng)),雖然它們功能類似,但不同的是:JMS是一個分布式消息發(fā)布機制,可以在多臺服務(wù)器上運行,處理能力要強大得多。而TaskEngine由于基于線程基礎(chǔ),因此不能跨JVM實現(xiàn)。可以說TaskEngine是一個微觀組件,而JMS則是一個宏觀架構(gòu)系統(tǒng)。JMS相關(guān)討論將在后面章節(jié)進行。

以上討論了Jive系統(tǒng)中觀察者模式的實現(xiàn),Jive使用線程比較基礎(chǔ)的概念實現(xiàn)了觀察者模式,當(dāng)然有助于了解J2EE很多底層的基礎(chǔ)知識,整個Web容器的技術(shù)實現(xiàn)就是基于線程池原理建立的。

JavaJDK則提供了比較方便的觀察者模式API——java.util.Observablejava.util.Observer,它們的用戶非常簡單,只要被觀察者繼承Observable,然后使用下列語句設(shè)置觀察點:

setChanged();

notifyObservers(name); //一旦執(zhí)行本代碼,就觸發(fā)觀察者了

而觀察者只要實現(xiàn)Observer接口,并實現(xiàn)update方法,在update方法中將被觀察者觸發(fā)后傳來的object進行處理。舉例如下:

網(wǎng)上商店中商品價格可能發(fā)生變化,如果需要在價格變化時,首頁能夠自動顯示這些降價產(chǎn)品,那么使用觀察者模式將方便得多。首先,商品是一個被觀察者:

public class product extends Observable{

  privatefloat price;

  publicfloat getPrice(){ return price;}

  publicvoid setPrice(){

   this.price=price;

 //商品價格發(fā)生變化,觸發(fā)觀察者

   setChanged();

   notifyObservers(newFloat(price));

  }

  ...

}

價格觀察者實現(xiàn)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);

    }

  }

}

這樣,一個簡單的觀察者模式就很容易地實現(xiàn)了。

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Jive 的緩存機制
表演需要開場舞?來看看這個~Jive
設(shè)計模式之Factory
Patterns in Java
JR - 研究文集 - Jive 研究
PSK微博20140331 | 生存裝備網(wǎng)
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服