<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    每日一得

    不求多得,只求一得 about java,hibernate,spring,design,database,Ror,ruby,快速開發
    最近關心的內容:SSH,seam,flex,敏捷,TDD
    本站的官方站點是:顛覆軟件

      BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
      220 隨筆 :: 9 文章 :: 421 評論 :: 0 Trackbacks

    2? Jive 與設計模式

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

    2.1? 設計模式

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

    GOF (設計模式作者簡稱)《設計模式》這本書第一次將設計模式提升到理論高度,并將之規范化,該書提出了 23 種基本設計模式。自此,在可復用面向對象軟件的發展過程中,新的大量的設計模式不斷出現。

    很多人都知道 Java 是完全面向對象的設計和編程語言,但是由于接受教育以及經驗的原因,大多數程序員或設計人員都是從傳統的過程語言轉變而來,因此在思維習慣上要完全轉變為面向對象的設計和開發方式是困難的,而學習設計模式可以更好地幫助和堅固這種轉變。

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

    由于設計模式概念比較抽象,對于初學者學習有一定的難度,因此結合 Jive 論壇系統學習設計模式將是一種很好的選擇。

    掌握了設計模式,將會幫助程序員或設計人員以更加可重用性、可伸縮性的眼光來開發應用系統,甚至開發通用的框架系統??蚣芟到y是構成一類特定軟件可復用設計的一組相互協作的類,主要是對應用系統中反復重用部分的提煉,類似一種模板,這是一種結構性的模板。

    框架通常定義了應用體系的整體結構、類和對象的關系等設計參數,以便于具體應用實現者能集中精力于應用本身的特定細節。框架強調設計復用,而設計模式最小的可重用單位,因此框架不可避免地會反復使用到設計模式。關于通用框架系統的設計開發將在以后章節中討論。

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

    2.2? ForumFactory 與工廠模式

    工廠模式是 GOF 設計模式的主要常用模式,它主要是為創建對象提供了一種接口,工廠模式主要是封裝了創建對象的細節過程,從而使得外界調用一個對象時,根本無需關心這個對象是如何產生的。

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

    以類 SampleOne 為例,要創建 SampleOne 的對象實例 :

    SampleOne sampleOne = new SampleOne();

    如果 Sample 類有幾個相近的類: SampleTwo SampleThree ,那么創建它們的實例分別是:

    SampleTwo sampleTwo = new SampleTwo();

    SampleThree sampleThree = new SampleThree();

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

    SampleIF sampleOne = new SampleOne();

    SampleIF sampleTwo = new SampleTwo();

    SampleIF sampleThree = new SampleThree();

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

    public class SampleFactory{

    ?? public abstract SampleIF creator();

    }

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

    public class SampleFactoryImp extends SampleFactory{

    ?? public SampleIF creator(){

    ??? // 根據其他因素綜合判斷返回具體產品

    ??? // 假設應該返回 SampleOne 對象

    ?????? return new SampleOne();

    }

    }

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

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

    前面已經討論在 Jive 中設置了論壇統一入口,這個統一入口就是 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 單態模式,將在 2.3 節討論

         if (factory == null) {

           synchronized(initLock) {

             if (factory == null) {

                 ... // 從配置文件中獲得當前 className

               try {

                   // 動態裝載類

                   Class c = Class.forName(className);

                   factory = (ForumFactory)c.newInstance();

               }

               catch (Exception e) {

                   return null;

               }

             }

           }

         }

         // 返回 proxy. 用來限制授權對 forum 的訪問

         return new ForumFactoryProxy(authorization, factory,factory.getPermissions(authorization));

       }

       // 創鍵產品接口 Forum 的具體對象實例

       public abstract Forum createForum(String name, String description)

       throws UnauthorizedException, ForumAlreadyExistsException;

    ?? // 創鍵產品接口 ForumThread 的具體對象實例

    public abstract ForumThread createThread(ForumMessage rootMessage)

    throws UnauthorizedException;

    // 創鍵產品接口 ForumMessage 的具體對象實例

    ?

    ?

    ??? public abstract ForumMessage createMessage();

       ....

    }

    ForumFactory 中提供了很多抽象方法如 createForum 、 createThread createMessage() 等,它們是創建各自產品接口下的具體對象,這 3 個接口就是前面分析的基本業務對象 Forum 、 ForumThread ForumMessage ,這些創建方法在 ForumFactory 中卻不立即執行,而是推遲到 ForumFactory 子類中實現。

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

    因此, Jive 論壇在統一入口處使用了抽象工廠模式來動態地創建論壇中所需要的各種產品,如圖 3-4 所示。

    3-4? ForumFactory 抽象工廠模式圖

    3-4 XmlForumFactory DbForumFactory 作為抽象工廠 ForumFactory 的兩個具體實現 , Forum 、 ForumThread ForumMessage 分別作為 3 個系列抽象產品接口 , 依靠不同的工廠實現方式 , 會產生不同的產品對象。

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

    細心的讀者也許會發現,在上面 ForumFactory 有一個 getInstance 比較令人費解,這將在 2.3 節進行討論。


    2.3? 統一入口與單態模式

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

    前面曾提到 ForumFactory Jive 提供客戶端訪問數據庫系統的統一入口。為了保證所有的客戶端請求都要經過這個 ForumFactory ,如果不使用單態模式,客戶端下列調用語句表示生成了 ForumFactory 實例:

    ForumFactory factory = new DbForumFactory();

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

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

    public class Singleton {

       private Singleton(){}

       // 在自己內部定義自己一個實例,是不是很奇怪

       // 注意這是 private ,只供內部調用

       private static Singleton instance = new Singleton();

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

       public static Singleton getInstance() {

         return instance;

       }

    }

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

    還有一種 Java 單態模式實現:

    public class Singleton {

       private Singleton(){}

       private static Singleton instance = null;

       public static synchronized Singleton getInstance() {

         if (instance==null)

           instance new Singleton()

         return instance;

       }

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

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

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

    Class c = Class.forName(className);

    factory = (ForumFactory)c.newInstance();

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

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

    2.4? 訪問控制與代理模式

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

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

    其中一個主要的控制目的是控制客戶端對實際對象的訪問權限。在 Jive 系統中,因為有角色權限的分別,對于 Forum 、 ForumThread FroumMessage 的訪問操作必須經過權限機制驗證后才能進行。

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

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

    ??????? }

    }

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

    Jive 中有下列幾個代理類:

    ·????????? ForumFactoryProxy :客戶端和 DbForumFactory 之間的代理。客戶端訪問 DbForumFactory 的任何方法都要先經過 ForumFactoryProxy 相應方法代理一次。以下意思相同。

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

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

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

    User Group 也有相應的代理類。

    由以上分析看出,每個數據對象都有一個代理。如果系統中數據對象非常多,依據這種一對一的代理關系,會有很多代理類,將使系統變得不是非常干凈,因此可以使用動態代理來代替這所有的代理類,具體實現將在以后章節討論。

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

    迭代( Iterator )模式是提供一種順序訪問某個集合各個元素的方法,確保不暴露該集合的內部表現。迭代模式應用于對大量數據的訪問, Java Collection API Iterator 就是迭代模式的一種實現。

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

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

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

    ResultFilter filter = new ResultFilter();? // 設置結果過濾器

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

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

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

    ?? while(threads.hasNext){

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

    ?? }

    3-5? 分頁顯示所有帖子

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

    public ForumThreadIterator threads(ResultFilter resultFilter) {

    ???? ForumThreadIterator iterator = forum.threads(resultFilter);

    ????? return new ForumThreadIteratorProxy(iterator, authorization, permissions);

    }

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

    public ForumThreadIterator threads(ResultFilter resultFilter) {

    // resultFilter 設置范圍要求獲得 SQL 查詢語句

    ?? String query = getThreadListSQL(resultFilter, false);?

    ?? // 獲得 resultFilter 設置范圍內的所有 ThreadID 集合

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

    ?? // 以下是計算查詢區域的開始點和終點

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

    在上面 threads 方法代碼中,第一步是先定制出相應的動態 SQL 查詢語句,然后使用這個查詢語句查詢數據庫,獲得查詢范圍內所有的 ForumThread ID 集合,然后在這個 ID 集合中獲得當前頁面的 ID 子集合,這是非常關鍵的一步。

    在這關鍵的一步中,有兩個重要的方法 getThreadListSQL getThreadBlock

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

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

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

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

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

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

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

    ForumThreadBlockIterator 重要方法 getElement 中實現了兩個功能:

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

    ·????????? 如果當前遍歷指針在當前頁面之內,根據 ID 獲得完整的數據對象,實現輸出;

    ForumThreadBlockIterator getElement 方法代碼如下:

    private Object getElement(int index) {

    ?? if (index < 0) {??????? return null;??????? }

    ?? // 檢查所要獲得的 element 是否在本查詢范圍內(當前頁面內)

    ?? if (index < blockStart ||

    index >= blockStart + DbForum.THREAD_BLOCK_SIZE) {??

    ????? try {

    ????????? // 從緩沖中獲得 Forum 實例

    ????????? DbForum forum = factory.cacheManager.forumCache.get(forumID);

    ????????? // 獲得下一頁的內容

    ????????? 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;

    ???? // 計算這個元素在當前查詢范圍內的相對位置

    ???? int relativeIndex = index % DbForum.THREAD_BLOCK_SIZE;

    ???? // Make sure index isn't too large

    ???? if (relativeIndex < threadBlock.length) {

    ??????? try {

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

    ??????????? element = factory.cacheManager.threadCache.get(

    ?????????? ?????????????threadBlock[relativeIndex]);

    ??????? } catch (ForumThreadNotFoundException tnfe) { }

    ???? }

    ???? return element;

    }

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

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

    public Object next() {

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

    ??????????? permissions);

    }

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

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

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

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

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

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

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

    public Iterator messages(ResultFilter resultFilter) {

    ?? Iterator iterator = thread.messages(resultFilter);

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

    }

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

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

    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 結果放入 forumList

    ??????? }

    ????? }catch (SQLException sqle) {

    ??????? sqle.printStackTrace();

    ????? } finally {

    ??????? …

    ??? }

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

    ? }

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

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

    2.6? 過濾器與裝飾模式

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

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

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

    先創建一個接口:

    public interface Work

    {

       public void insert();

    }

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

    public class SquarePeg implements Work{

       public void insert(){

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

       }

    }

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

    public class Decorator implements Work{

       private Work work;

       // 額外增加的功能被打包在這個 List

       private ArrayList others = new ArrayList();

       public Decorator(Work work)

       {

         this.work=work;

         others.add(" 打洞 ");?? // 準備好額外的功能

       }

       public void insert(){

         otherMethod();

         work.insert();

       }

       public void otherMethod()

       {

         ListIterator listIterator = others.listIterator();

         while (listIterator.hasNext())

         {

           System.out.println(((String)(listIterator.next())) + " 正在進行 ");

         }

    }

    }

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

    Work squarePeg new SquarePeg();

    Work decorator = new Decorator(squarePeg);

    decorator.insert();

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

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

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

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

    前面討論過,在 Jive 中,有主要幾個對象 ForumFactory Forum 以及 ForumThread ForumMessage ,它們之間的關系如圖 3-2 所示。因此帖子內容 ForumMessage 對象的獲得是從其上級 FroumThread 的方法 getMessage 中獲取,但是在實際代碼中, ForumThread 的方法 getMessage 委托 ForumFactory 來獲取 ForumMessage 對象。看看 ForumThread 的子類 DbForumThread getMessage 代碼:

    public ForumMessage getMessage(long messageID)

    ???????? throws ForumMessageNotFoundException

    {

    ???? return factory.getMessage(messageID, this.id, forumID);

    }

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

    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 {

    ? // 應用全局過濾器

    ???? filterMessage = filterManager.applyFilters(message);

    ??????????????? Forum forum = getForum(forumID);???????????????

    ??????????????? // 應用本論壇過濾器

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

    ??????????? }

    ??????????? catch (Exception e) { }

    ??????? return filterMessage;

    }

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

    Work decorator = new Decorator(squarePeg);

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

    FilterManager filterManager =? new DbFilterManager();

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

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

    3-6? 裝飾模式

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

    public void insert(){

         otherMethod();

         work.insert();

    }

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

    HTMLFilter 中還使用了原型( Prototype )模式,原型模式定義是: 用原型實例指定創建對象的種類,并且通過復制這些原型創建新的對象。按照這種定義, Java clone 技術應該是原型模式的一個實現。

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

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

    2.7? 主題監測與觀察者模式

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

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

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

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

    看看 Jive WatchManager 類:

    public interface WatchManager {

    ??? // 正常監察類型,用戶在這個主題更新后再次訪問時,會明顯地發現

    ??? public static final int NORMAL_WATCH = 0;

    ??? ?// 當主題變化時,通過電子郵件通知用戶

    ??? public static final int EMAIL_NOTIFY_WATCH = 1;

    ??? // 設置一個主題被觀察的時間,默認為 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 的內容

    ??? 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;

    ??? …

    ?

    ??? // 為某個主題創建一個觀察者

    ??? public void createWatch(User user, ForumThread thread, int watchType)

    ??????????? throws UnauthorizedException;

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

    ??? public void deleteWatch(User user, ForumThread thread, int watchType)

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

    ??? public Iterator getWatchedForumThreads(User user, int watchType)

    ??????????? throws UnauthorizedException;

    ??? // 判斷一個用戶是否在觀察監視該主題

    ??? public boolean isWatchedThread(User user, ForumThread thread, int watchType)

    ??????????? throws UnauthorizedException;

    ??? …

    }

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

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

    protected void notifyWatches(ForumThread thread) {

    ???? //If watches are turned on.

    ??? if (!emailNotifyEnabled) {

    ??????????? return;

    ???? }

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

    ???? EmailWatchUpdateTask task = new EmailWatchUpdateTask(this, factory, thread);

    ???? TaskEngine.addTask(task);

    ?}

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

    DbForumThread addMessage 的最后一行有一句:

    factory.watchManager.notifyWatches(this);

    這其實是調用了 DbWatchManager notifyWatches 方法,因此確實是在增加新帖子時觸發了該帖子的所有觀察者。

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

    public class TaskEngine {

    ??? // 任務列表

    ??? private static LinkedList taskList = null;

    ??? // 工作數組

    ??? private static Thread[] workers = null;

    ??? private static Timer taskTimer = null;

    ??? private static Object lock = new Object();

    ?

    ??? static {

    ??????? // 根據配置文件初始化任務啟動時間

    ??????? taskTimer = new Timer(true);

    ??????? // 默認使用 7 個線程來裝載啟動任務

    ??????? workers = new Thread[7];

    ??????? taskList = new LinkedList();

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

    ???????????? // TaskEngineWorker 是個簡單的線程類

    ??????????? TaskEngineWorker worker = new TaskEngineWorker();

    ??????????? workers[i] = new Thread(worker);

    ??????????? workers[i].setDaemon(true);

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

    ??????? }

    ??? }

    ??? //TaskEngineWorker 內部類

    ??? private static class TaskEngineWorker implements Runnable {

    ??????? private boolean done = false;

    ??????? public void run() {

    ??????????? while (!done) {

    ??????????????? // 運行 nextTask 方法

    ??????????????? nextTask().run();

    ??????????? }

    ????? ??}

    ??? }

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

    ??? private static Runnable nextTask() {

    ??????? synchronized(lock) {

    ??????????? // 如果沒有任務,就鎖定在這里

    ??????????? while (taskList.isEmpty()) {

    ??????????????? try {

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

    ??????????????? } catch (InterruptedException ie) { }

    ??????????? }

    ??????????? // 從任務列表中取出第一個任務線程

    ??????????? return (Runnable)taskList.removeLast();

    ??????? }

    ??? }

    ??? public static void addTask(Runnable r) {

    ??????? addTask(r, Thread.NORM_PRIORITY);

    ??? }

    ??? // 這是任務列表 Queue 的生產者

    ??? public static void addTask(Runnable task, int priority) {

    ??????? synchronized(lock) {

    ??????????? taskList.addFirst(task);

    ??????????? // 提醒所有鎖在 lock 這里的線程可以運行了

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

    ??????????? lock.notifyAll();

    ??????? }

    ??? }

    ??? …

    }

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

    這個 TaskEngine 建立的模型非常類似 JMS Java 消息系統),雖然它們功能類似,但不同的是: JMS 是一個分布式消息發布機制,可以在多臺服務器上運行,處理能力要強大得多。而 TaskEngine 由于基于線程基礎,因此不能跨 JVM 實現??梢哉f TaskEngine 是一個微觀組件,而 JMS 則是一個宏觀架構系統。 JMS 相關討論將在后面章節進行。

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

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

    setChanged();

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

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

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

    public class product extends Observable{

       private float price;

       public float getPrice(){ return price;}

       public void setPrice(){

       this.price=price;

      // 商品價格發生變化,觸發觀察者

       setChanged();

       notifyObservers(new Float(price));

       }

       ...

    }

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

         }

       }

    }

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

    posted on 2006-08-31 12:26 Alex 閱讀(761) 評論(0)  編輯  收藏 所屬分類: java
    主站蜘蛛池模板: 国产精一品亚洲二区在线播放 | 曰批免费视频播放在线看片二| 国产亚洲精久久久久久无码| 在线a毛片免费视频观看| 巨波霸乳在线永久免费视频 | 午夜亚洲WWW湿好爽| 亚洲最大AV网站在线观看| 日本19禁啪啪无遮挡免费动图| 香蕉免费一级视频在线观看| 亚洲国产精品无码久久九九大片 | 美女尿口扒开图片免费| 中国china体内裑精亚洲日本| 亚洲韩国在线一卡二卡| 国产亚洲综合久久系列| 久久久久亚洲av毛片大 | 免费人成动漫在线播放r18| 亚洲色大成WWW亚洲女子| 亚洲另类精品xxxx人妖| 亚洲丝袜美腿视频| 久久精品亚洲综合| 亚洲AV永久精品爱情岛论坛| 丁香五月亚洲综合深深爱| 国产成人毛片亚洲精品| 亚洲人成色7777在线观看不卡| 国产午夜免费福利红片| 免费无码又爽又刺激高潮视频| 91在线视频免费观看| 中文字幕在线成人免费看| 中文字幕免费在线看电影大全 | 日韩视频在线精品视频免费观看| 95老司机免费福利| 84pao国产成视频免费播放| 精品无码AV无码免费专区| 最近中文字幕免费mv在线视频| 999任你躁在线精品免费不卡| 91青青国产在线观看免费| 91热成人精品国产免费| 91香蕉视频免费| 好爽又高潮了毛片免费下载 | 色视频在线观看免费| 亚州**色毛片免费观看|