目錄
摘要 ……………………………………………………………………2
文獻綜述 ………………………………………………………………3
第一章 前言……………………………………………………………6
第二章 OOP的基本原則及發展方向
第一節 軟件的可維護性與可復用性…………………………6
第二節 六條OOP設計原則 ……………………………………7
第三節 AOP的興起…………………………………………… 8
第三章 J2EE系統的架構設計
第一節J2EE中常用的概念……………………………………10
第二節 MVC架構 ………………………………………………12
第三節 分布式架構……………………………………………13
第四章 數據持久層的設計
第一節 業務對象的持久化……………………………………14
第二節 數據訪問對象設計模式………………………………15
第三節 ORM框架的原理和重要性 ……………………………16
第四節 數據持久層……………………………………………19
第五節 數據庫連接池、緩存及系統性能的提升 ……………21
第六節 Java反射技術 ………………………………………22
第五章 J2EE架構中各層的數據表示方法
第一節 MVC三層體系結構中的數據表示要求 ………………23
第二節 J2EE系統中各層數據表示的設計 …………………24
第六章 設計模式的概念與幾種常用的J2EE設計模式
第一節 設計模式的概念………………………………………25
第二節 工廠創建模式與單例模式……………………………27
第三節 使用工廠模式實現DAO ………………………………31
總結 ……………………………………………………………………33
結束語 …………………………………………………………………34
多層J2EE系統的架構與模式設計
【摘要】 J2EE提供了一套完整的基于標準化模塊的服務組件,它能自動的處理大多數應用程序的細節,而不需要復雜的編程,因此簡化了復雜的企業級應用程序的開發。本文首先考察企業級應用的一般概念和需求,然后簡要闡述面向對象程序設計的基本原則,并結合軟件工程的思想來討論多層的J2EE應用架構,分析它們滿足企業級應用的方式,,再通過講述常用的幾種Java設計模式和Java反射技術來說明如何實現這些應用架構。
【關鍵詞】 模型-視圖-控制,對象關系映射,業務對象,面向方面編程,數據訪問對象,設計模式
The Framework of Multitier J2EE System and Design Pattern
【abstracts】The J2EE simplifies enterprise applications by basing them on standardized, modular components, by providing a complete set of services to those components, and by handling many details of application behavior automatically,without complex programming. This paper reviews the general concept and the requirement of enterprise application, elaborates the general principle of object oriented programming briefly. We combine the idea of Software-Engineering to discuss the framework of multitier J2EE, and meanwhile analyze how they can satisfy the demand of enterprise applications. At last, this paper shows how to implement those frameworks of multitier J2EE by introducing some kinds of Java design pattern and the Java reflection technology.
【key words】MVC,ORM, BO, AOP, DAO,Design pattern.
【文獻綜述】
計算機軟件是人類心靈和智慧在虛擬空間中的投射。軟件的性能是人類能力的擴展,它的活動就是人類心智活動的反映。軟件直接表達出設計者對目標的理解,對用戶的期待,以及對自己的定位。人們在自己的環境中不斷發現問題和尋找問題的解決方案的時候,發現有一些問題及其解決方案不斷變換面孔重復出現,但在這些不同的面孔后面有著共同的本質,這些共同的本質就是模式。著名建筑工程學家Christopher Alexander所著《建筑的永恒之道》( The Timeless Way of Building)和他發展出來的模式理論涵蓋科學,心理,藝術和哲學,不僅適用于建筑工程學,而且適用于軟件工程學以及任何其他的工程學。
今天的企業軟件可以由多個不同的部分組成,但企業已經認識到,只要符合企業利益,很有必要將各個分散的系統進行良好的集成,以盡可能相互支持,總的來說企業希望對集成后的企業級軟件的具體應用如下:
1. 通過集成企業的客戶支持和本身的產品知識,企業可以利用WEB為它的客戶提供更新更好的服務。
2. 將企業售貨機聯網,企業可以獲得更多的在線客戶。
3. 將銷售管理系統和存貨系統相鏈接,企業可以設計特定的低成本的Web銷售渠道,這樣可以進入未曾涉足的市場領域。
4. 如果給企業員工所使用的服務提供一個前端,例如內部辦公用品訂貨系統,將它與會計系統連接在一起,企業就可以降低總體開支并提高員工的工作效率。
5. 在線使用企業HR系統,可以讓員工根據他們自己的健康狀況進行更多的選擇,這樣可以降低企業整體的管理費用。
6. 使企業的人力資源密集型操作自動化,并使它可用于任何時間任何地點,在降低整體運營費用的同時,企業還可以給它的客戶提供更好的服務。
按企業對企業級軟件的要求,一個企業級應用系統(J2EE)肯定會是一個服務于商業目的,處理企業業務信息,數據的軟件系統,因此大概可以總結出以下五方面的特征:有復雜的業務邏輯,有大量持久化數據,與多種外部系統相關聯有較高的性能要求,在運行時需要隨時監控,管理,應該能夠實時記錄,觀察系統運行情況。修改系統配置。
以前的企業應用,集中式的單層(single tier)應用程序占有主導地位。在軟件中,層是一個抽象概念,它的主要目的是通過將軟件分解成獨立的邏輯層,幫助我們理解與特定應用程序相關聯的體系結構。從應用程序的角度看,單層應用程序的最大問題在于,它將表示,業務邏輯和數據都混合在一起。客戶機-服務器方法通過將表示和一些業務邏輯分別移至單獨的層中,緩解了上述主要問題的影響,不過從應用程序的角度來看,業務邏輯和表示依然很混亂。N層(n-tier)方法可以取得更好的整體平衡,它將表示邏輯與業務邏輯從底層數據中分離開來,以滿足特定的需求。單單采用面向對象開發技術后只可以實現部分代碼重用,原因之一是對象都細粒度化,正是因為細粒度對象間更緊密的耦合狀態,從而便利大范圍的重用變得很困難。分層化的組件設計就是為了解決這個問題。與對象不同,軟件組件是在更高的抽象級中設計的,可以提供一個完整的功能或服務。組件間的耦合更為松散。利用組件的接口,可以將組件迅速組合在一起以構建更大的企業級應用程序。
近年來,人們已開發出了各種不同的幫助理解的組件模型,例如,Microsoft的ActiveX,后來的COM編程接口,和現在興起的.net FrameWork,SUN Microsystems的applet和JavaBeans,Enterprise JavaBeans(EJB),其中EJB是J2EE的一部分。
Sun Microsystems把Java2平臺組織成三個特定的,引人矚目的版本:微型版(J2ME),標準版(J2SE)和企業版(J2EE)。在這些產品中,J2EE與開發企業級Java應用聯系最緊密。J2EE為開發復雜的,分布式企業級Java應用定義了一套體系結構。
J2EE最初是由Sun Microsystems在1999年中期發布的,其正式發布則在1999年后期。J2EE仍然較新,其依次發布的版本間仍然存在著重大的改變,特別是在EJB方面。該平臺是建立在Java“一次編寫,隨意運行”的理念上的,它通過一組技術和一套API實現。
N層體系結構的概念已經出現一段較長的時間了,并已成功地應用于構建企業級應用程序。Sun在Java中采用n層開發模型,并引入特定功能,允許更容易地開發服務器端可伸縮的、基于Web的企業級應用程序,從而在這個領域提供了Java自身所缺少的關鍵成分。
為什么要使用J2EE呢?它不是太新并且功能未經證實,它能提供什么?難道只是一種一時的技術狂熱嗎?在J2EE出現之前,JDBC API早已建立好了,可選用的輕量級的,可維護的servlet技術也已出現。除了這些,J2EE還提供了一些有前景的優點,它讓開發人員關注開發業務邏輯,不用預先詳細了解執行環境而把精力放到實現系統上,以及創建在硬件平臺和操作系統(OS)間更容易銜接的系統。企業級軟件開發是一項復雜的任務,需要具備許多不同領域的廣泛知識。例如,一項典型的企業級應用程序開發工作可能要求你熟悉進程間的通信問題、安全問題、數據庫特定訪問查詢等。
J2EE企業級開發平臺鼓勵在系統開發、部署和執行之間作一個清晰的劃分。此開發人員可以將部署細節留給部署人員處理,如實際的數據庫名稱和存放位置、主機持有配置屬性等。J2EE讓系統可通過Java和J2EE而不是底層系統API被訪問,從而支持硬件和OS無關性。由于這種原因,遵循J2EE體系結構技術規范的企業級系統可以非常容易地在硬件系統和不同的OS之間銜接。
在企業級開發領域,雖然面對Microsoft .net強大的挑戰,但是J2EE由于上述優點,并且相對說來比較成熟,已經占據了企業級開發的大部分市場,并隨著技術的進步、新的J2EE版本的發布、開源社區龐大自由開發者的支持,將會使企業級開發變得更高效,更快速,更高質量,更易于維護。
第一章 前言
J2EE核心技術有十三種,它們和J2EE API覆蓋了企業級Java開發的廣泛領域。在企業級Java開發工作中要用到的J2EE的方方面面知識是不太可能的。比較常用的有容器,servlet, JSP, EJB等。容器是一種運行在服務器上的軟件實體,用于管理特定類型的組件。它為開發J2EE組件提供了執行環境。通過這些容器,J2EE體系結構就能在開發和部署間提供無關性,并在不同類型的中間層服務器間提供可移植性。servlet是一些可生成動態內容的Web組件。它們是當今在www上看到的最常用的J2EE組件之一。它們提供了一種有效的機制,用于基于服務器的業務邏輯和基于Web的客戶端之間的交互,還可為通用的CGI腳本方法提供一種輕型且更易于管理的替代方法。JSP是另一種類型的J2EE Web組件,它是從servlet技術發展而來的。事實上,一部分JSP編譯進servlet并在servlet容器中執行。EJB技術規范是J2EE平臺的最核心的部分。它為構建可伸縮、分布式、基于服務器的企業級Java應用組件提供了一種綜合性的組件模型。文章將結合這幾種主要的組件技術來講述構建J2EE系統的一般過程。
第二章 OOP的基本原則及發展方向
第一節 軟件的可維護性與可復用性
通常認為,一個易于維護的系統,就是復用率較高的系統;而一個復用較好的系統,就是一個易于維護的系統。也就是說一個系統的設計目標應該具有如下性質:可擴展性,靈活性,可插入性。
常聽人說一個項目開發結束只完了這個項目的三分之一,可見系統的可維護的重要性。導致一個系統可維護性降低主要有四個原因:過于僵硬,過于脆弱,復用率低,黏度過高。通過良好的軟件復用,可以提高軟件的生產效率,控制生產成本,并能提高軟件的質量,改善系統的可維護性,提高系統的靈活性和可插入性。
在面向對象的設計里,可維護性復用是以設計原則和設計模式為基礎的,下一節介紹面向對象設計的基本原則。
第二節 六條OOP設計原則
OOP設計原則是提高軟件系統的可維護性和可復用性的指導性原則,Java是一門純面向對象的設計語言,因此我們在使用Java開發J2EE系統時必須遵守OOP設計的基本原則。
這些設計原則首先都是復用的原則,遵循這些設計原則可以有效地提高系統的復用性,同時提高系統的可維護性:
l OCP開閉原則:一個軟件實體應當對擴展開放,對修改關閉
l LSP(里氏代換原則):它是繼承復用的基石
l DIP(依賴倒轉原則):要依賴于抽象,不要依賴于具體
l ISP(接口隔離原則):一個類對另一個類的依賴性應當是建立在最小接口上
l CARP(合成/聚合復用原則):要盡量使用合成/聚合,盡量不要使用繼承
l LOD(迪米特法則):一個對象應該對其他對象有盡可能少的了解
通過擴展已有的軟件系統,可以提供新的行為,以滿足對軟件的新需求,使變化中的軟件系統有一定的適應性和靈活性。而已有的軟件模塊,特別是最重要的抽象層模塊不能再修改,這就使變化莫測中的軟件系統有一定的穩定性和延續性。具有這些優點的軟件系統是一個在高層次上實現了復用的系統,也是一個易于維護的系統。
里氏代換要求凡是基類型使用的地方,子類型一定適用,因此子類必須具備基類型的全部接口。
傳統的過程性系統的設計辦法傾向于使高層次的模塊依賴于低層次的模塊;抽象層次依賴于具體層次。抽象層次包含的是應用系統的商務邏輯和宏觀的,對整個系統來說是重要的戰略性決定,是必然性的體現;而具體層次則含有一些次要的與實現有關的算法和邏輯,以及戰術性的決定,帶有相當大的偶然性選擇。具體層次的代碼是會經常有變動的,不能避免出現錯誤。抽象層次依賴于具體層次,使許多具體層次的細節的算法變化立即影響到抽象層次的宏觀商務邏輯,導致微觀決定宏觀,戰術決定戰略,偶然決定必然。從哲學意義上面講這是很荒唐的事情,倒轉原則就是要把這個錯誤的依賴關系倒轉過來。
如果兩個類不必彼此直接通信,那么這兩個類就不應當發生直接的相互作用。如果其中的一個類需要調用另一個類的某一個方法的話,以可通過第三者轉發這個調用。
“針對接口編程”現在已經逐漸成為廣大OOP程序員的共識,成為OOP設計思想的集中體現。一門語言即使不提供Interface這樣的關鍵字,它也需要在很大程序上模擬出接口的功能。典型的如C++,通過聲明一個只有純虛函數的子類來模擬接口功能。不過這種模擬也就僅限于此,比如對于Java中的動態代理,抽象基類似乎就無能為力了。
OOP經過二十多年的發展,逐漸取代面向過程程序設計,已經相當成熟。OOP的設計思想主要體現在下以幾個方面:
(1) 針對抽象編程,不針對具體編程。這是依賴倒轉原則所要求的。換言之,應當針對抽象類編程,不要針對具體子類編程,這一原則點出了抽象類對代碼利用的一個最重要的作用。
(2) 使用繼承達到軟件復用的目的。在Java中,繼承有兩種,一種是接口繼承,一種是實現繼承。第二種繼承常常很容易被濫用。只要可能,盡量使用合成,而不要使用繼承來達到復用的目的。
(3) 使用模板方式模式,它是類的行為模式,準備一個抽象類,將部分邏輯以具體方法以及具體構造子的形式實現,然后聲明一些抽象方法來迫使子類實現剩余邏輯。不同的子類可以以不同的方式實現這些抽象方法,從而對剩余的邏輯有不同的實現。
第三節 AOP的興起
軟件工程的發展史實際上就是抽象的發展史。隨著軟件越來越復雜,相應地我們也提高了編程語言的方法的抽象級別。因此,我們經歷了從C到C++到Java,從結構化方法到面向對象的設計,從類到設計模型又到體系結構框架這一系列的改變,并且這個改變仍然在繼續著。
AOP(Aspect Oriented Programming) 面向方面編程是一種超越OOP的編程模型,它允許程序員將橫切關注點(散布在多個模塊中的一致概念如同步處理,持久化,日志等都是典型的橫切關注點)封裝成清晰的可重用模塊,然后通過組合這些模塊和功能性組件獲得系統的實現。AOP通過促進另一種模塊性補充了OOP,便利我們可以很自然地處理一些傳統的OOP不能解決的問題。
在J2EE應用開發中,我們主要使用AOP的攔截(interception)能力,它提供了在任何對象的方法調用前/后加入自定義行為的能力。這使得我們可以處理企業應用中的橫切關注點(同時作用于多個對象的關注點),并且仍然保持強類型,而不需要改變方法簽名。例如,可以在一個應該具有事務的方法調用前開始一個事務,在方法返回時提交或者回滾。使用AOP可以把與事務管理相關的重復勞動放進一個框架內。
作為一個開發者,我每天大部分的時間面對的是數據庫的存取,JNDI資源的訪問,事情的聲明釋放,以及各處文件的讀取等等,這些面對不同業務的同樣操作。但是,在執行一步操作的時候往往我們需要配置一個比較完整的環境,如果JSP/Servlet容器,EJB容器等。在當今J2EE主流以發展輕量級構件的時代,我們有機會從擺脫這種重復的勞動,使用上輕量級的構件技術。
輕量級容器依靠反轉控制(Inversion of Control. IoC)模式或依賴注入(Dependency Injection)現在是Java社區非常熱門的話題,它實際上是針對接口編程這個OOP概念的進一步深化。利用IoC模式能夠很好的解決代碼調用者和被調用者之間的依賴關系,不僅可以靈活的裝配我們的業務對象,更重要的是它讓我們有機會在一個完整的環境中進行工作,使我們的業務開發不用再考慮環境因素。如果用得著某幾項J2EE服務,就可以使用他們,比如,想用JTA就用JTA,想用數據庫連接池就用連接池。這樣,讓我們的應用代碼在J2EE環境下,不受任何運行環境的束縛。更有特色的是,輕量級容器借助AOP的橫切服務甚至可以讓我們的代碼段中不包含try/catch這樣的常用代碼成例。因為這樣這種異常處理是與環境相關的。
從概念上面來說,JNDI查找,事務,安全之類的基礎設施都是與業務邏輯橫切的。由于業務組件都是輕量級容器負責管理生命周期,使用者只要是通過容器進行組件訪問,我們就應該讓容器插入額外的代碼來管理橫切基礎設施。然而,將這些基礎設計進行外部聲明,而不讓他們進入到應用代碼之中,才是一個系統實現可插入性的最好的實現辦法。
在J2EE輕量級容器中,Spring無疑是最流行,發展得最好的,它提供的IoC容器不僅可以幫助我們完成我們的目標,而且它是開源的。
那么Spring如果將我們從重復的勞動中解放出來呢?一般,在我們寫數據庫接口時,需要訪問數據庫的時候都需要創建一個Connection,再聲明一個statement,然后再進行具體的數據庫操作,事務處理完閉之后,還要用try/catch語句將statement,connection close()掉。在JDBC2.0規范中,我們通常所做的方法是通過容器過(Tomcat5.0,Jboss,Weblogic都提供很好的JNDI支持)定義資源,并暴露為全局性的JNDI對象,這樣,客戶端的代碼將直接通過JNDI訪問這些資源,顯然,應用代碼將與JNDI基礎設施綁定在了一起,也就是說應用代碼與環境相關了,很難借助偽造的資源對象來進行單元測試,而且這些的代碼既不易重用,也不能在J2EE環境以外運行。
第三章 J2EE系統的架構設計
第一節 J2EE中常用的概念
在軟件業邁向組件裝配工業(software component industry) 的過程中﹐不斷發現組件設計者對其組件應用場合的預想環境與應用軟件師的軟件體系結構常常無法很好地整合起來﹐導致應用軟件開發人員難以靈活地復用他人設計好的組件,造成軟件組件工業發展上的瓶頸。OOP軟件專家也逐漸認識到其問題是來自于軟件主架構上的不兼容。軟件主架構的重要性并非今天才呈現出來﹐20多年前軟件大師Fred.P.Brooks 就提到:軟件開發者之間﹐他們設計的理念必須一致才能共同創造出簡單易用的軟件,同時他也強調軟件主架構在達到概念一致的過程中,合作居于核心角色。現在,開發者們在項目開始時不是討論要不要使用架構,而是討論究竟該使用什么樣的架構。
(1) 體系結構(Architecture)
體系結構也可稱為架構,所謂軟件架構﹐根據Perry 和Wolfe之定義:Software Architecture = {Elements,Forms, Rationale / Constraint },也就是軟件主架構 = {組件元素,元素互助合作之模式,基礎要求與限制}。Philippe Kruchten采用上面的定義﹐并說明主架構之設計就是:將各組件元素以某些理想的合作模式組織起來﹐以達成系統的基本功能和限制。
(2) 架構(Framework)
框架也可稱為應用架構,框架的一般定義就是:在特定領域基于體系結構的可重用的設計。也可以認為框架是體系結構在特定領域下的應用。框架比較出名的例子就是MVC(模型-視圖-控制)。
(3) 庫(Library)
庫應該是可重用的、相互協作的資源的集合,供開發人員進行重復調用。它與框架的主要區別在于運行時與程序的調用關系。庫是被程序調用,而框架則調用程序。常見的庫有Java API,Apache組織提供的Java開發包。
(4) 設計模式(Design Pattern)
設計模式應該很熟悉,尤其四人幫所寫的書更是家喻戶曉。“四人幫”將模式描述為“在一定的環境中解決某一問題的方案”。這三個事物 — 問題、解決方案和環境 — 是模式的基本要素。
(5) 平臺(Platform)
由多種系統構成,其中也可以包含硬件部分。
在J2EE系統開發過程中,大致可以分為五大步驟:需求、分析、設計、編碼、測試。而體系結構是軟件的骨架,是最重要的基礎。體系結構是涉及到每一步驟中。一般在獲取需要的同時,就應該開始分析軟件的體系結構。體系結構現在一般是各個大的功能模塊組合成,然后描述各個部分的關系,J2EE平臺已經為我們提供了整個軟件系統的體系結構。
架構是體系結構中每個模塊中相對細小的結構。如需要表示Web技術,就會用到MVC架構,而Web功能只是整個軟件體系中的一個功能模塊。每個架構可以有許多個實例,如用Java實現的MVC架構Struts。
而在架構之下就是設計模式,設計模式一般是應用于架構之中,也可以說是對架構的補充。架構只是提供了一個環境,需要我們填入東西。無論是否應用了設計模式,都可以實現軟件的功能,而正確應用設計模式,是對前人軟件設計思想或實現方法的一種繼承。
第二節 MVC架構
上一節中提到基于Web開發的MVC架構目前在J2EE的世界內空前繁榮。在這些架構中,老牌的有Struts、Web work。新興的有Spring MVC、Tapestry、JSF等。這些大多是著名團隊的作品,都提供了較好的層次分隔能力,在實現良好的MVC 分隔的基礎上,通過提供一些現成的輔助類庫,促進了生產效率的提高。
在這么多J2EE架構中如何選擇一個適合自己項目的架構呢?什么是衡量一個架構設計是否優秀的標準?從實際Web產品研發的角度而言(而非純粹設計上,擴展性上,以及支持特性上的比較),目前Struts 也許是第一選擇。它擁有成熟的設計,同時,也擁有最豐富的信息資源和開發群體。從較偏向設計的角度出發,WebWork2 的設計理念更加先進,其代碼與Servlet API 相分離,這使得單元測試更加便利,同時系統從B/S結構轉向C/S接口也較為簡單。另外,對基于模板的表現層技術(Velocity、Free Maker和XSLT)的支持,也為程序員提供了除JSP之外的更多的選擇(Struts也支持基于模板的表現層技術,只是實際中不太常用)。而對于Spring而言,首先,它提供了一個相當靈活和可擴展的MVC實現,與WebWork2相比,它在依賴注入方面、AOP 等方面更加優秀,但在MVC 框架與底層構架的分離上又與WebWork2 存在著一定差距,Spring 的MVC 與Servlet API 相耦合,難于脫離Servlet容器獨立運行,在這點的擴展性上,比Webwork2稍遜一籌。Spring對于Web應用開發的支持,并非只限于框架中的MVC部分。即使不使用其中的MVC實現,我們也可以從其他組件,如事務控制、ORM模板中得益。同時,Spring也為其他框架提供了良好的支持,很容易就可以將Struts與Spring搭配使用。因此,對于Spring在Web應用中的作用,應該從一個更全面的角度出發。
J2EE系統采用三層的MVC架構之后,其解決的主要問題無外乎以下幾部分:
(1) 將Web頁面中的輸入元素封裝為一個(請求)數據對象。
(2) 根據請求的不同,調度相應的邏輯處理單元,并將(請求)數據對象作為參數傳入。
(3) 邏輯處理單元完成運算后,返回一個結果數據對象。
(4) 將結果數據對象中的數據與預先設計的表現層相融合并展現給用戶或將其持久化。
這樣的J2EE系統將具有下以幾個優點:
(1) 多個視圖能共享一個模型。在MVC架構中,模型響應用戶請求并返回響應數據,視圖負責格式化數據并把它們呈現給用戶,業務邏輯和表示層分離,同一個模型可以被不同的視圖重用,所以大大提高了代碼的可重用性。
(2) 模型是自包含的,與控制器和視圖保持相對獨立,所以可以方便地改變應用程序的數據層和業務規則。由于MVC的三個模塊相互獨立,改變其中一個不會影響其它的兩個,所以依據這種設計思想能構造良好的松耦合的構件。
(3) 控制器提高了應用程序的靈活性和可配置性。
使用MVC需要精心的計劃,由于它的內部原理比較復雜,所以需要花費一些時間去理解它。將MVC運用到J2EE應用程序中,會帶來額外的工作量,增加應用的復雜性,所以MVC不適合小型應用程序。
面對大量用戶界面,業務邏輯復雜的大型應用程序,MVC將會使軟件在健壯性,代碼重用和結構方面上一個新的臺階,尤其是商業軟件的高度可變性。在我實際開發過的項目(一個電子商務網站與一個搜索引擎,和正在開發的一個進銷存管理系統)中,我都應用到了Struts,項目相對來說比較成功,通過實踐認識到并不是系統中加入了Struts的類庫和標簽就說明使用了MVC Struts,Struts僅僅是一種思想,你也可以不用它的類庫模擬MVC環境,軟件的開應該要做到靈活多變。
第三節 分布式架構
J2EE的兩大特征就是分層與分布。據統計,大多數中小企業級應用都用不上分布式,因此本文側重介紹多層,簡要的介紹一下分布式的適用范圍。
“記住分布式計算機的第一法則:不要分布你的對象!”—(Martin Fowler Patterns of Enterprise Application Architecture)。我們真正需要分布式應用嗎?其實我們早已意識到大部分J2EE應用程序,特別是WEB應用程序,并不能從分布式體系架構中受益。甚至相反,由于前期的過渡設計,在根本無需分布式的應用中大量使用分布式技術,不但沒有享受到分布式的優點,而且還帶來了不同應用層之間昂貴的遠程調用,引入了復雜的遠程訪問期間基礎架構和分布式編程。同時,我們也必須明白邏輯層的分層遠比物理層的分隔重要。選擇分布式也就是選擇EJB,選擇EJB也就是選擇了重量級組件。
也就是說,一些應用選擇分布式架構應該有足夠的理由。如果真正是明確的業務需求,這屬于應有的分布式應用(internal distribution),你需要根據特定的情形選擇適當的分布式機制,如果是為了靈活性,在項目的未來某個時期也許要遠程輸出某些功能,應用程序的架構應該是允許引入選擇性的分布式應用(selective distribution),而不是在項目早期就進行過渡設計,直接使用分布式架構。
第四章 數據持久層的設計
第一節 業務對象的持久化
業務對象(Business Object),是對真實世界的實體的軟件抽象,它可以代表業務領域中的人,地點,事物或概念。業務對象包括狀態和行為,判斷一個類是否成為業務對象的一個重要標準,是看這個類是否同是擁有狀態和行為。業務對象具有的特征:包含狀態和行為,代表業務領域的人,地點,事物或概念。可以重用。通常分為三類業務對象:
(1) 實體業務對象,代表人,地點,事物或概念的組件,在分布式應用中可以作為實體EJB,在更一般的Web應用中可以作為包含狀態和行為的Java Bean。
(2) 過程業務對象,代表應用中的業務過程或流程,通常依賴于業務對象。在J2EE應用中,它們通常作為會話EJB或是消息驅動EJB。在非分布式應用中,我們可以使用具有管理和控制應用行為的常規Java Bean。
(3) 事件業務對象,代表應用中的一些事件,如異常,警告或是超時。
業務對象提供了通用的術語概念,不管是技術人員還是非技術人員都可以共享并理解他們,并且它可以隱藏實現細節,對外只暴露接口。
業務對象在Java編程中可以分兩類,分別是普通純Java對象(pure old java object or plain ordinary java object or what ever POJO)和持久對象(Persistent Object PO)。
持久對象實際上必須對應數據庫中的entity,所以和POJO有所區別。比如說POJO是由new創建,由GC(垃圾收集器)回收。但是持久對象是insert數據庫創建,由數據庫delete刪除的。持久對象的生命周期和數據庫密切相關。另外持久對象往往只能存在一個數據庫Connection之中,Connection關閉以后,持久對象就不存在了,而POJO只要不被GC回收,總是存在的。由于存在諸多差別,因此持久對象PO(Persistent Object)在代碼上肯定和POJO不同,起碼PO相對于POJO會增加一些用來管理數據庫entity狀態的屬性和方法。
持久化意味著通過手工或其化方式輸入到應用中的數據,能夠在應用結束運行后依然存在,即使應用運行結束或者計算機關閉后,這些信息依然存在,不管什么樣的系統都需要數據的持久化。我們將需要持久化處理的BO稱之為PO,當應用中的業務對象在內存中創建后,它們不可能永遠存在,在從內存中清除之前,要被持久化到關系數據庫中。
第二節 數據訪問對象設計模式
上一節中提到PO在從內存中清除之前要被持久化到關系數據庫中,如何做到這一點呢?OOP是當今的主流,但是不得不使用關系型數據庫,因此在企業級開發中,對象-關系的映射(Object-Relation Mapping,簡稱ORM)就是很關鍵的一部分了。圍繞ORM和數據持久化的方式,軟件領域通常采用一種數據訪問對象(Data Access Object,簡稱DAO)設計模式。DAO模式提供了訪問關系型數據庫系統所需要的所有操作接口,其中包括創建數據庫,定義表,字段和索引,建立表間的關系,更新和查詢數據庫等。DAO模式將底層數據庫訪問操作與高層業務邏輯分離開,對上層提供面向對象的數據訪問接口(接口的實現通常使用抽象工廠設計模式來實現)。在DAO的實現中,可以采用XML語言來配置對象和關系型數據之間的映射。在映射數據庫表時,值對象類及其子類所構成的樹形結構被用來映射一個數據庫表,該繼承樹通過XML 配置文件對應數據庫中的單個表,這使得底層的關系型的數據庫表結構能夠面向對象模型所隱藏,另外,由于面向對象設計方法中類的可繼承性,采用繼承樹對應一個表的策略使得該映射策略極易擴展,并且能夠將一個復雜的數據表轉化成若干簡單的值對象來表示,提高了系統的可維護性和可修改性。
對于一般的J2EE應用,可以直接通過JDBC編程來訪問數據庫。JDBC可以說是訪問持久層最原始,最直接的方法。在企業級應用開發中,可以通過JDBC編程,來開發自己的DAO API,將數據庫訪問期間操作封裝起來,供業務層統一調用。因此DAO所封裝的接口在團隊開發中顯得非常的重要,幾乎是項目成敗的關鍵。
DAO模式在系統中所處的位置
DAO的實現通常使用工廠方法模式,工廠方法模式將創建實例的工作與使用實例的工作分開,也就是說,讓創建實例所需要的大量初始化工作從簡單的構造函數中分離出去。只需要調用一個統一的方法,即可根據需要創建出各種對象的實例,對象的創建方法不再用編碼到程序模塊中,而是統一編寫在工廠類中。這樣在系統進行擴充修改時,系統的變化僅存在于工廠類內部,而絕對不會對其他對象造成影響。
第三節 ORM框架的原理和重要性
如果數據模型非常復雜,那么直接通過JDBC編程來實現持久化框架需要有專業的知識,這樣可以直接使用采用第三方提供的持久化框架。如Hibernate,Ibatis,JDO。
在使用ORM框架時也需要遵守OOP設計原則和MVC框架的基本原則,都應該確保框架沒有滲透到應用中,應用的上層組件應該和ORM框架保持獨立。ORM追求的目標就是要 PO在使用上盡量和POJO一致,對于程序員來說,他們可以把PO當作POJO來用,而感覺不到PO的存在。ORM框架就是要做到維護數據庫表記錄的PO完全是一個符合Java Bean規范的POJO,沒有增加別的屬性和方法。
同是ORM框架,但不同的ORM工具實現的方式有所不同,目前主要有兩種ORM工具競爭,JDO與Hibernate,以下介紹一下它們的工作機理。
JDO的實現ORM方法如下:
(1) 編寫POJO。
(2) 編譯POJO。
(3) 使用JDO的一個專門工具,叫做Enhancer,一般是一個命令行程序,手工運行,或者在ant腳本里面運行,對POJO的class文件處理一下,把POJO替換成同名的PO。
(4) 在運行時運行的實際上是PO,而不是POJO。
該方法有點類似于JSP,JSP也是在編譯期被轉換成Servlet來運行的,在運行期實際上運行的是Servlet,而不是JSP。
Hibernate的實現方法如下:
(1) 編寫POJO,通常我們可以通過Hibernate官方提供的MiddleGen for Hibernate 和Hibernate Extension工具包,很方便的根據現有數據庫,導出數據庫表結構,生成XML格式的ORM配置文件和POJO。
(2) 編譯POJO。
(3) 直接運行,在運行時,由Hibernate的cglib庫利用Java的反射技術動態把POJO轉換為PO。
由此可以看出Hibernate是在運行時把POJO的字節碼轉換為PO的,而JDO是在編譯期轉換的。一般認為JDO的方式效率會稍高,畢竟是編譯期轉換嘛。但是Hibernate的作者Gavin King說cglib(Hibernate類庫中的一個必需使用的jar包)的效率非常之高,運行期的PO的字節碼生成速度非常之快,效率損失幾乎可以忽略不計。實際上運行時生成PO的好處非常大,這樣對于程序員來說,是無法接觸到PO的,PO對他們來說完全透明。可以更加自由的以POJO的概念操縱PO。Hibernate查詢語言(Query Language),即HQL,HQL是一種面向對象的查詢語言,不同于SQL(結構化查詢語言),它具備繼承、多態和關聯等特性。
Hibernate是從PO實例中取values的,所以即使Session關閉,也一樣可以get/set,可以進行跨Session的狀態管理。
在N層的J2EE系統中,由于持久層和業務層和Web層都是分開的,此時Hibernate的PO完全可以當作一個POJO來用,在各層間自由傳遞,而不用去管Session是開還是關。如果你把這個POJO序列化的話,甚至可以用在分布式環境中。
因此,在較為常用的數據持久方案中,Hibernate的ORM框架是最優秀的,下面是對各種持久方案的比較:
l 流行的數據持久層架構:
Business Layer <-> Session Bean <-> Entity Bean <-> DB
l 為了解決性能障礙的替代架構:
Business Layer <-> DAO <-> JDBC <-> DB
l 使用Hibernate來提高上面架構的開發效率的架構:
Business Layer <-> DAO <-> Hibernate <-> DB
通過實際開發和測試,分析出以上三個架構的優缺點:
(1) 內存消耗:采用JDBC的架構無疑是最省內存的,Hibernate的架構次之,EJB的架構最差。
(2) 運行效率:如果JDBC的代碼寫的非常優化,那么JDBC架構運行效率最高,但是實際項目中,這一點幾乎做不到,這需要程序員非常精通JDBC,運用Batch語句,調整PreapredStatement的Batch Size和Fetch Size等參數,以及在必要的情況下采用結果集cache等等。而一般情況下程序員是做不到這一點的。因此Hibernate架構表現出最快的運行效率。EJB的架構效率會差的很遠。
(3) 開發效率:在有Eclipse、JBuilder等開發工具的支持下,對于簡單的項目,EJB架構開發效率最高,JDBC次之,Hibernate最差。但是在大的項目,特別是持久層關系映射很復雜的情況下,Hibernate效率高的驚人,JDBC次之,而EJB架構很可能會失敗。
在我工作時所做過的一些項目的過程中,例如電子商務網站 (通常是在寫數據庫查詢和存儲過程接口時),面對大量的數據反復存儲調用,工作量之大無法忍受,這個很煩惱的持久層開發的問題一直在困擾我。持久層的開發現在一般來說要么用CMP,要么用JDBC+DAO。 CMP需要的成本太高,對于這種無需對象分布的系統并不實用,而JDBC+DAO也存在很多的困難,尤其是數據庫表很龐大關系很復雜(通常的商業軟件的數據庫都非常的龐大)的時候,我很難做到把關系表記錄完整的映射到PO的關系上來,這主要體現在多表的關系無法直接映射到對持久對象的映射上來,可能是一個表映射多個持久對象,有可能是多個表映射一個PO,更有可能的是表的某些字段映射到一個持久對象,但是另外一些字段映射到別的持久對象上。而且即使這些問題都處理好了,也不能直接按照對象的方式來對持久對象(PO)編程,因為存在1:N關系的持久對象的查詢其實就是1+n次對數據庫的SQL,等于是完全拋棄了對象設計,完全是按照表字段進行操作,但在這種情況下重復編程量簡直無法想象。更重要的是這樣做會為系統的成敗留下非常大的隱患,因為一般系統是從需求設計,系統設計這樣自頂而下的,如果都到了詳細設計階段被持久層映射問題限制,不得不自底向上修改設計方案,又回到了過程化設計上來,嚴重了違背了依賴倒轉原則(DIP)。 很明顯我并不是第一個遇到這種問題的人,其實這是一個經典的問題:對象和關系的映射問題。自從OOP流行以來,就一直存在這個難題,所以才有人提出將關系數據庫進行重新設計,也會有對象型數據庫的出現,但實際上關系數據庫并沒有被淘汰,于是就只能在上層的應用層找解決方案。ORM產品正是為這種解決方案而設計的。 一個好的ORM應該有如下的特點:
(1) 開源和免費的License,任何人都可以在需要的時候研究源代碼,改寫源代碼,進行功能的定制。
(2) 輕量級封裝,避免引入過多復雜的問題,調試容易,也減輕程序員的負擔。
(3) 具有可擴展性,API開放,當功能不夠用的時候,用戶可以自行編碼進行擴展。
(4) 開發者活躍,產品有穩定的發展保障。
Hibernate符合以上ORM的標準,它的文檔也是非常有特色的地方(而且提供了簡體中文版),它不僅僅是 Hibernate的功能介紹那么簡單,它實際上是一個持久層設計的最佳實踐的經驗總結,文檔里面的例子和總結全部都是最佳設計的結晶。SUN公司最近發布的EJB3.0中的實體EJB仿效了Hibernate這種輕量級組件技術。
第四節 數據持久層
數據持久化問題關鍵在于它的復雜性。 復雜性是應用開發過程中最令人頭疼的一個問題。每當在一個應用中增加一個功能時,它的復雜性通常呈幾何級的增長。這種復雜性往往導致程序的開發無法再繼續下去。
專家將應用開發過程產生的復雜性分為兩類,即非本質的(accidental) 和本質的(essential)。本質的復雜性是對于解決目標問題所必然產生的復雜性,非本質的復雜性是由于選擇了不適當的開發工具和設計工具而產生的復雜性。對于一個功能確定的程序來講,本質的復雜性是確定的,而非本質的復雜性則是沒有限制的。因此,一個應用的開發要想較順利地取得成功,就需要盡可能地減少非本質的復雜性。
設計模式使人們可以更加簡單方便地復用成功的設計和體系結構。將已證實的技術表述成設計模式,也會使新系統開發者更加容易理解其設計思路。
衡量一個系統優秀與否的關鍵因素,除了能夠滿足用戶需求外還有如下方面:首先是靈活性。靈活性意指這種結構或模式不依賴于任何實際應用,應該與操作系統、應用程序無關。提供獨立的結構,可以提供最大的重用。其次是可擴展性。隨著業務的擴展,新的業務不斷增加,業務邏輯自然增加,系統必然會進行修改或添加相應 功能模塊。再次是可配置性。最后是安全性。
數據持久層的設計采納了多種設計模式,最大限度的降低了系統內部各模塊、子系統間的耦合性,使得系統相對易于擴展,并且能夠在進行改變時,保證持久層的業務邏輯層相對穩定,基本不需要因持久層的調整改變而進行邏輯層的變動。
根據數據源不同,數據訪問也不同。根據存儲的類型(關 系數據庫、面向對象數據庫等)和供應商不同,持久性存儲(比如數據庫)的訪問差別也很大。當業務組件或表示組件需要訪問某數據源時,它們可以使用合適的 API來獲得連接性,以及操作該數據源。但是在這些組件中包含連接性和數據訪問代碼會引入這些組件及數據源實現之間的緊密耦合。組件中這類代碼依賴性使應用程序從某種數據源遷移到其它種類的數據源將變得非常麻煩和困難,當數據源變化時,組件也需要改變,以便于能夠處理新類型的數據源。
數據持久層通過調整抽象工廠(Abstract Factory)模式和工廠方法(Factory Method) 模式,使DAO模式達到了很高的靈活度。 數據持久層使用數據訪問對象來抽象和封裝所有對數據源的訪問。DAO管理著與數據源的連接以便于檢索和存儲數據,DAO實現了用來操作數據源的訪問機制,內部封裝了對 Hibernate數據操縱、事務處理、會話管理等API的封裝。外界依賴于DAO的業務組件為其客戶端使用DAO提供了更簡單的接口,DAO完全向 客戶端隱藏了數據源實現細節。由于當低層數據源實現變化時,DAO向客戶端提供的接口不會變化,采用該設計模式允許DAO調整到不同的存儲模式,而不會影 響其客戶端或業務組件,即使將來不再采用Hibernate作為關系映射框架,上層客戶端也不會受到任何影響。另外,DAO還充當組件和數據源之間的適配 器的角色。
當底層存儲隨著實現的變化而變化時,該策略可以通過使用抽象工廠模式實現。抽象工廠可以基于工廠方法實現而創建,并可使用工廠方法實現。該策略提供一個DAO的抽象工廠對象,其中該對象可以構造多種類型的具體的DAO工廠,每個工廠支持一種不同類型的持久性存儲實現。一旦你獲取某特定實現的具體DAO工廠,可以使用它來生成該實現中所支持和實現的DAO。
第五節 數據庫連接池、緩存及系統性能的提升
通過使用框架和設計模式對數據訪問的封裝,極大的提高了軟件的可擴展可維護性,同時也帶來了系統的性能問題,J2EE系統通過以下一些技術解決這些問題。
緩存(Cache),對于數據庫來說,廠商的做法往往是在內存中開辟相應的區域來存儲可能被多次存取的數據和可能被多次執行的語句,以使這些數據在下次被訪問時不必再次提交對DBMS的請求和那些語句在下次執行時不必再次編譯。同樣,數據持久層采用緩存技術來保存已經從數據庫中檢索出來的部分常用數據。客戶端訪問持久層時,持久層將首先訪問緩存,如果能夠命中則直接從緩存中提取數據,否則再向數據庫發送提取數據的指令。這種設計能夠大幅度地提高數據訪問速度。
數據庫連接池(Connection Pool),池是一個很普遍的概念,和緩沖存儲有機制相近的地方,都是縮減了訪問的環節,但它更注重于資源的共享。對于訪問數據庫來說,建立連接的代價比較昂貴,因此,數據持久層建立了“連接池”以提高訪問的性能。數據持久層把連接當作對象,整個系統啟動后,連接池首先建立若干連接,訪問本來需要與數據庫連接的區域,都改為和池相連,池臨時分配連接供訪問使用,結果返回后,訪問將連接交還。這種設計消除了JDBC與數據源建立連接的延時,同時在應用級提供了對數據源的并發訪問。
享元模式(Flyweight),面向對象語言的原則就是一切都是對象,但是如果真正使用起來,有時對象數可能顯得很龐大,比如,數據庫中的記錄,如果以每條記錄作為一個對象,提取幾千條記錄,對象數就是幾千,這無疑相當耗費內存。數據持久層依據享元模式設計了若干元類,封裝可以被共享的類。這種設計策略顯著降低了系統的內存消耗。
第六節 Java反射技術
在不了解的情況下要想操縱一個Java對象,需要使用Java的反射技術。反射技術的出現為Java開發工具和服務容器項目的開發提供了新的技術手段,例如第四章中提到的Hibernate在運行時使用cglib類利用Java反射技術動態地將POJO轉化為PO。
Java的J2SE開發包中包含一個特殊的部分:Java反射接口,被放置在java.lang.reflect包中。反射使Java的類,包和接口實現了自我描述和動態操作的功能,這些功能對于Java應用程序的開發者來說,似乎過于基礎化了,但是反射提供的不僅僅是是一項功能那么簡單,通過反射,Java提供了新的一種非直接參與調用的方式,打破了一些固有的限制。反射已經為多個成功的項目所運用,產生了出乎意料的成功效果。配合一些把基礎的反射功能擴展的工具項目,Java的動態功能得以出色地發揮出來。Hibernate也得益于反射,反射使Hibernate的代碼更簡潔有效,在操作時增強了功能。
反射技術機制使很多原來困難的任務變得容易實現了,反射技術的功能很強大,但是開發者在實際的開發過程中,并不一定非要使用反射技術不可,需要根據具體的情形來選擇技術方案。即使一定要使用反射技術,也需要一些已有的工具的支持。
第五章 J2EE架構中各層的數據表示
第一節MVC三層體系結構中的數據表示要求
三層體系結構中的數據表示首先要做到的是數據的低耦合度,以Struts三層體系結構為例,三層數據的表示應該如下:
- Web層的數據表示是ActionFormBean,數據來源于html Form post
- 持久層的數據表示是PO,其數據來源于數據庫,持久層的數據表示例如CMP
按照MVC的設計原則,層與層之間應該保持相對獨立,數據的傳遞在不同的層之間通常利用Java Bean來創建數據傳輸對象(Data Transfer Object,簡稱DTO),從技術的角度上面來說,采用DTO來傳輸數據可以減少傳輸數據的冗余,提高傳輸效率,更重要的是實現了各個層之間的獨立,使每個層分工明確。模型層負責業務邏輯,視圖層負責向用戶展示模型狀態。采用DTO,模型層對視圖層屏蔽了業務邏輯細節,向視圖層提供可以直接顯示給用戶的數據。在一個規范的J2EE架構中,不同層的數據表示應該被限制在層內,而不應該擴散到其它層,這樣可以降低層間的耦合性,提高J2EE架構整體的可維護性和可擴展性。比如說Web層的邏輯進行了修改,那么只需要修改Web層的Form Bean結構,而不需要觸動業務層和持久層的代碼修改。同樣的,當數據庫表進行了小的調整,那么也只需要修改持久層數據表示,而不需要觸動業務層代碼和Web層代碼。
先來談談ActionFormBean和持久層的PO之間的重大區別:在簡單的應用中,ActionFormBean和PO幾乎是沒有區別,所以很多人干脆就是用ActionFormBean來充當PO,于是 ActionFormBean從JSP頁面到Servlet控制層再到業務層,然后穿過持久層,最后一直映射到數據庫表。系統以后需要修改其工作量無法想像。但是在復雜的應用中,ActionFormBean和PO是分離的,它們也不可能一樣。ActionFormBean是和網頁里面的Form表單一一對應的,Form里面有什么元素,Bean里面就有什么屬性。而PO和數據庫表對應,因此如果數據庫表不修改,那么PO也不會修改,如果頁面的流程和數據庫表字段對應關系不一致,那么又如何能夠使用ActionFormBean來取代PO呢?例如一個用戶注冊頁面要求注冊用戶的基本信息,因此HTML Form里面包含了基本信息屬性,于是你需要一個ActionFormBean來一一對應,每個Bean屬性對應一個文本框或者選擇框等。而用戶這個持久對象的屬性和ActionFormBean有什么不同呢?它會有一些ActionFormBean所沒有的集合屬性,如用戶的權限屬性,用戶的組屬性,用戶的帖子等等。另外還有可能的是在ActionFormBean里面有3個屬性,分別是用戶的First Name, Middle Name, Last Name,或者干脆在User這個持久對象中就是一個 Name 對象屬性。假設注冊頁面原來只要提供First Name,那么ActionFormBean就這一個屬性,后來要提供全名,要改ActionFormBean,加兩個屬性。但是這個時候PO是不應該修改,因為數據庫沒有改。
第二節 J2EE系統中各層數據表示的設計
在一個完整的N層J2EE系統中應該如何進行合理數據表示設計呢?Struts是這樣做的:
JSP(View) ---> Action Form Bean (Module) ---> Action(Control)Action
Form Bean是Web層的數據表示,它和HTML頁面Form對應,只要Web頁面的操作流程發生改變,它就要相應的進行修改,它不應該也不能被傳遞到業務層和持久層,否則一旦頁面修改,會一直牽連到業務層和持久層的大面積的代碼進行修改,Action就是它的邊界。
Action(Web Control) ---> Business Bean ---> DAO ---> ORM --->DB
PO則是業務層和持久層的數據表示,它在業務層和持久層之間進行流動,它不應該也不能被傳遞到Web層的View中去,而ActionServlet就是它的邊界。
再來看看整個架構的數據流程:當用戶通過瀏覽器訪問網頁,提交了一個頁面。于是Action得到了這個Form Bean,它會把Form Bean屬性讀出來,然后構造一個PO對象,再調用業務層的Bean類,完成了注冊操作,重定向到成功頁面。而業務層Bean收到這個PO對象之后,調用DAO接口方法,進行持久對象的持久化操作。當用戶查詢某個會員的信息的時候,他用全名進行查詢,于是Action得到一個UserNameFormBean包括了3個屬性,分別是first name, middle name, last name,然后Action把UserNameFormBean的3個屬性讀出來,構造Name對象,再調用BO,把Name對象傳遞給BO,進行查詢。BO取得Name(注意: Name對象只是User的一個屬性)對象之后調用DAO接口,返回一個User的PO對象,注意這個User不同于在Web層使用的UserFormBean。然后BO把User對象返回給Action。Action得到User對象之后,把User的基本屬性取出,構造UserFormBean,然后把UserFormBean request.setAttribute(…),然后重定向到查詢結果頁面。查詢頁面拿到request對象里面的ActionFormBean,自動調用tag顯示。
第六章 設計模式的概念與幾種常用的J2EE設計模式
第一節 設計模式的概念
程序設計是思維具體化的一種方式,是思考如何解決問題的過程,設計模式是在解決問題的過程中,一些良好思路的經驗集成。最早提到設計模式,人們總會提到Gof的著作,它最早將經典的23種模式集合在一起說明,對后期學習程序設計,尤其是對從事物件導向程序的人們起了莫大的影響。對于Java設計師來說,源代碼的匯聚按尺度來分,可以分為由Java語句組成的“代碼模式”,由Java類和對象組成的“設計模式”,由大尺度的構件組成的“架構模式”。
Gof 提到的OOP中設計模式涵蓋三大類。
(1) 創建Creational 模式
對象的產生需要消耗系統資源,所以如何有效率的產生、管理與操作對象,一直都是值得討論的課題, Creational 模式即與對象的建立相關,在這個分類下的模式給出了一些指導原則及設計的方向。
l 簡單工廠(Simple Factory) 模式
l 抽象工廠(Abstract Factory) 模式
l 建造(Builder) 模式
l 工廠方法(Factory Method) 模式
l 原始模型(Prototype) 模式
l 單例(Singleton) 模式
l 多例(Multition)模式
(2) 結構Structural 模式
如何設計對象之間的靜態結構,如何完成物件之間的繼承、實現與依賴關系,這關系到系統設計出來是否健壯(robust):是否易懂、易維護、易修改、耦合度低等。Structural 模式正如其名,其分類下的模式給出了在不同場合下所適用的各種對象關系結構。
l 缺省適配(Default Adapter) 模式
l 適配(Adapter)模式
l 橋梁(Bridge) 模式
l 合成(Composite) 模式
l 裝飾(Decorator) 模式
l 門面(Fa?ade) 模式
l 享元(Flyweight) 模式
l 代理(Proxy) 模式
(3) 行為Behavioral 模式
對象之間的合作行為構成了程序的最終行為,對象之間若有良好的行為互動,不僅使得程序執行時更有效率,更可以讓對象的職責更為清晰、整個程序的動態結構(對象之間的互相調用)更有彈性。
l 責任鏈(Chain of Responsibility) 模式
l 命令(Command) 模式
l 解釋器(Interpreter) 模式
l 迭代子(Iterator) 模式
l 調停者(Mediator) 模式
l 備忘錄(Memento) 模式
l 觀察者(Observer) 模式
l 狀態(State) 模式
l 策略(Strategy) 模式
l 模版方法(Template Method) 模式
l 訪問者(Visitor) 模式
第二節 工廠創建模式與單例模式
l 簡單工廠(Simple Factory)模式:又稱靜態工廠方法模式(Static Factory Method Pattern)
l 工廠方法(Factory Method)模式:又稱多態性工廠(Polymorphic Factory)模式或虛擬構造子(Virtual Constructor)模式
l 抽象工廠(Abstract Factory)模式:又稱工具箱(Toolkit)模式
下面以簡單工廠設計模式為例說明代碼模式在Java編程中的應用。
從上圖可以看出,簡單工廠模式涉及到工廠角色,抽象產品角色以及具體產品角色等三個角色:
l 工廠類(Creator)角色:擔任這個角色的是工廠方法模式的核心,含有與應用緊密相關的商業邏輯,工廠類在客戶端的直接調用下創建產品對象,它往往由一個具體Java類來實現
l 抽象產品(Product)角色:擔任這個角色的類是由工廠方法模式所創建的對象的父類,或它們共同擁有接口。抽象產品角色可以用一個Java接口或者Java抽象類實現
l 具體產品(Concrete Product)角色:工廠方法模式所創建的任何對象都是這個角色的實例,具體產品角色由一個具體Java類實現
例如我們要在程序中產生兩個對象:一個圓形與一個方形,建立時要同時設定它們的中心位置,然后它們會負責畫出自己。我們可以設計一個 Shape Factory工廠類,負責創建指定的對象,調用者只管指定對象名稱與中心位置,而不管這些對象如何產生,對象生成后的中心位置設定被隱藏于 Shape Factory工廠類中。
如上圖所示的,Main代表了客戶的角色,它只依賴于表層業務調用,而不關心特定的實例,實例的細節由Shape Factory完成,我們以一個簡單的程序來實現上面這個UML類圖:
public interface IShape { //建立IShape接口
public void setCenter(int x, int y);
public void draw(); //定義一個接口方法
}
public class Circle implements IShape {
private int x;
private int y;
public void setCenter(int x, int y) {//實現IShape接口setCenter方法
this.x = x;
this.y = y;
}
public void draw() { //實現IShape接口draw()方法
System.out.println("Circle center at ("
+ x + ", " + y + ")");
}
}
public class Square implements IShape {
private int x;
private int y;
public void setCenter(int x, int y) { //實現IShape接口setCenter方法
this.x = x;
this.y = y;
}
public void draw() {//實現IShape接口draw()方法
System.out.println("Square center at ("
+ x + ", " + y + ")");
}
}
public class ShapeFactory {
public static IShape createShapeAt(String name, int x, int y) {
try {
IShape shape
= (IShape) Clas.forName(name).newInstance();//使用Java反射技術獲得名為name產品實例,并讓IShape接口持有此實例對象
shape.setCenter(x,y);
return shape;
}
catch(Exception e) {
System.out.println(
"Sorry! No such class defined!"); return null;
}
}
}
public class Main {
public static void main(String args[]) {
// 產生一個圓形并且顯示它
ShapeFactory.createShapeAt("Circle", 10, 10).draw();
System.Out.println();
// 產生一個方形并顯示它
ShapeFactory.createShapeAt("Square", 20, 25).draw();
}
}
客戶只要面對Factory,客戶依賴于產品的調用介面,產品的具體實例是可以與客戶隔開的,它們也是可以互換的。簡單工廠模式是工廠方法模式與抽象工廠模式的一種特殊情況,關于它們的實現可以參考相關資料。
單例模式確保某一個類只有一個實例,而且自行實例化并向整個系統提供這個實例。單例模式在系統設計時使用非常廣泛,常用的資源管理器一般使用此模式,在一些線程安全的情況下使用也比較多,如數據庫連接池訪問接口,屬性文件管理等。
以上面的UML圖為例來說明單例模式的使用方法,我們可以在第一次需要實例時再創建對象,也就是采用所謂的Lazy Initialization懶漢式:
public class Singleton {
private static Singleton instance = null;
private Singleton() {
// .......
}
public static Singleton getInstance() { //獲得系統唯一實例的靜態方法
if (instance == null) instance = new Singleton();
return instance;
}
// ...... 其它代碼
}
上面的代碼適用于單線程的程序,在多線程的程序下,以下的寫法在多個線程的競爭資源下,將仍有可能產生兩個以上的對象,存在線程安全性問題,例如下面的情況:
Thread1: if(instance == null) // true
Thread2: if(instance == null) // true
Thread1: instance = new Singleton(); // 產生一個實例
Thread2: instance = new Singleton(); // 又產生一個實例
Thread1: return instance; // 返回一個實例
Thread2: return instance; // 又返回一個實例
在多線程環境下,為了了避免資源同時競爭而導致如上產生多個實例的情況,我們加上同步機制:
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
synchronized static public Singleton getInstance() {//為獲得系統唯一實例的靜態方法加上線程同步機制,保證此方法同時只能被一線程調用
if (instance == null) instance = new Singleton();
return instance;
} }
第三節 使用工廠模式實現DAO
由于J2EE模式眾多,篇幅有限,這里只概要介紹其中的一種使用工廠模式實現數據訪問對象。
使用數據訪問對象(DAO)來抽象和封裝所有對數據源的訪問。 DAO管理著與數據源的連接以便于檢索和存儲數據,DAO實現了用來操作數據源的訪問機制。依賴于DAO的業務組件為其客戶端使用DAO提供了更簡單的接口,DAO完全向客戶端隱藏了數據源實現細節。由于當低層數據源實現變化時,DAO向客戶端提供的接口不會變化,所以該模式允許DAO調整到不同的存儲模式,而不會影響其客戶端或業務組件。重要的是,DAO充當組件和數據源之間的適配器。
當低層存儲隨著實現的變化而變化時,策略可以通過使用抽象工廠模式而實現。抽象工廠可以基于工廠方法實現而創建,并可使用工廠方法實現,該策略提供一個 DAO的抽象工廠對象,其中該對象可以構造多種類型的具體的DAO工廠,每個工廠支持一種不同類型的持久性存儲實現。一旦你獲取某特定實現的具體DAO工廠,你可以使用它來生成該實現中所支持和實現的DAO,如下面類圖所示。
例如在一個項目中有四張表,分別是用戶信息表(user Info),論壇表(Forum),主題表(Topic),回復表(Reply)。先創建與數據庫表相對應組件Java Bean,然后為每一個數據庫表配置一個數據庫操作接口。如下所示:
UserInfo訪問對象接口
package com.webclass.dao;
public interface UserInfoDao {
//設置帖子屬性時,用戶的屬性
public void upUserState(String state,String usrName);
// 查詢用戶是否存在判斷(返回整個用戶)
public UserInfo selectUser(String usrName);
//設置用戶權限
public void upUserRole(int i ,String usrRole,int usrId);
//修改用戶信息
public boolean editUser(UserInfo user);
……………………………等操作邏輯方法
}
Forum訪問對象接口
package com.webclass.dao;
public interface ForumDao {
…………………操作邏輯方法
}
Reply訪問對象接口
package com.webclass.dao;
public interface ReplyDao {
………………操作邏輯方法
}
Topic訪問對象接口
package com.webclass.dao;
public interface TopicDao {
………………操作邏輯方法
}
接下來就是寫具體類實現以上其接口,而接口方法不依賴于類的具體實現。數據庫的訪問對象已經創建好之后,應用程序將調用這些對象進行數據庫操作,如何調用這些對象呢?我們使用一個工廠類來專門負責這些對象的創建工作:
public class DBFactory {
public static ForumDao dBForum=null;
public static ReplyDao dBReply=null;
public static TopicDao dBTopic=null;
public static UserInfoDao dBUserInfo=null;
synchronized public static ForumDao getDBForum(){
if(dBForum==null) dBForum=new DBForum();
return dBForum;
}
synchronized public static ReplyDao getDBReply(){
if(dBReply==null) dBReply=new DBReply();
return dBReply;
}
synchronized public static TopicDao getDBTopic(){
if(dBTopic==null) dBTopic=new DBTopic();
return dBTopic;
}
synchronized public static UserInfoDao getDBUserInfo(){
if(dBUserInfo==null) dBUserInfo=new DBUserInfo();
return dBUserInfo;
}
}
這樣應用程序的上層組件就可以直接調用DBFactory工廠對象所持有的靜態對象來進來數據庫操作了,更重要的是當底層數據庫訪問操作發生改變時只需修改訪問對象,而不會波及到上層組件,使用軟件健壯性和靈活性大大提高。
總結
即使是利用當今最先進的軟件平臺J2EE,開發企業應用程序仍然是一個難題,因為軟件的復雜性和脆弱性無法回避。J2EE通過J2EE API提供了技術與服務的高層抽象,使企業開發得到了一定的簡化。但是,僅僅知道J2EE API是不夠的。要設計良好的體系結構,得到高質量的應用程序,要知道何時如何正確的使用J2EE API,就要使用更為實用的方式。
計算機技術更新發展很快,新技術方面由于經驗缺乏,通常我們要自己猜測如何正確使用這些技術,要通過不斷的試驗,直到找出最佳的方法,最佳的方法顯然是從實踐中得到的,不是發明出來的,而是發現和不斷完善的。
工程學產中的一大原則就是總結經驗和利用實踐證明行之有效的方案,軟件開發也是這樣。經驗有助于更快更順利的建立良好的解決方案,從而節省成本,提高質量。唯一的問題就是要需要獲得經驗,但這個經驗可以從別人那里間接獲得,而不一定需要自己的直接經驗。別人的經驗如何描述?多年來,模式已經成為收集,規范和分析某些情境中常見問題的有效方法,學習模式可以節省自己的時間,知道如何根據許多開發人員的間接經驗進行合理的設計。
結束語
在撰寫這篇論文期間,我查閱了大量的技術書籍和文章,學到了很多的知識,使自己的技術水平得到了提高。同時得到了袁健美老師的悉心指導和幫助,在此,我向袁健美老師表達誠摯的謝意。
參考文獻:
1. Bruce Eckel. Think in Java第三版 .電子工業出版社. 北京. 2002
2. Khawar Zaman Ahmed. J2EE和UML開發Java企業級應用程序. 清華大學出版社. 2003
3. Crig Bery. 實用J2EE設計模式編程指南. www.china-pub.com. 2003
4. 孫衛琴. Tomcat與Java Web開發技術. 電子工業出版社. 2004
5. 孫衛琴. 精通Struts. 電子工業出版社. 2004
6. Christopher Alexande. The Timeless Way of Building. Arrangement with Oxford University Press, Inc. 2003
7. 夏昕. Hibernate開發指南. www.china-pub.com. 2004