<style type="text/css"> <!-- body { margin-top: 0px; } --> </style>
圖2.2
l
轉自:http://www.lvjiyong.com/books/OODesigner/
轉自:http://www.lvjiyong.com/books/OODesigner/
孫亞民
目錄
第一部分 綜述 4
第1章本書會討論什么內容 5
第2章系統的分層結構 8
2.1.簡述 8
2.2.設計的原則和評判標準 9
2.3.應用服務層的內容 10
2.4.數據實體的表示 11
2.5.數據的存取方式 15
2.6.業務邏輯的處理 18
2.7.業務服務的提供 20
2.8.層的部署和層間交互 20
2.9.剪裁和取舍 21
2.10.小結 21
第二部分 應用服務層的設計 23
第3章數據和對象 24
3.1數據的形態 24
3.2對象/關系型映射 26
3.3對象的狀態 28
Transient 28
Persistent-new 29
Persistent-dirty 29
Persistent-clean 29
Persistent-deleted 29
第4章 O/R Mapping的一般做法 31
第5章設計一個O/R Mapping框架 40
5.1封裝數據庫訪問層 40
5.2設計映射 48
5.3 對繼承的支持 55
5.4設計對象操縱框架 61
5.5實現對象操縱框架 66
第6章面向方面編程 71
6.1 AOP概念 71
6.2 Websharp AOP的使用 73
6.2.1.使用AOP實現松散耦合 73
6.2.2.使用AOP組合兩個業務邏輯 76
6.3 Websharp AOP的實現 76
6.3.1 AspectObject抽象類 78
6.3.2 IAspect接口 78
6.3.3 AspectManagedAttribute 78
6.3.4 定義AspectProxy類 80
6.3.5 其他一些輔助類 80
6.3.6 配置文件 80
6.4 關于AOP和過濾器 81
6.5 小結 82
第7章接口 83
第8章事務處理 86
8.1 事務的基本概念 86
8.2 實際開發中可用的事務處理方式 88
第9章性能優化 101
第三部分 用戶界面層設計 102
第10章 界面層的功能劃分 103
第11章 界面設計模式 104
11.1 MVC模式 104
11.2 頁面控制器 107
第12章 動態代碼生成和編譯技術 108
12.1 Emit 108
12.2 CodeDom 108
第13章 遠程過程訪問的客戶端整合 111
Web Service 111
.Net Remoting 112
Websharp Service Locator的主要接口 114
Websharp Service Locator的配置文件 114
如何使用Websharp Service Locator 116
LocalAssemblyLocator 的Hello World例子 116
Hello World 的WebServiceLocator例子 118
Websharp Service Locator的實現 120
目前的進展 120
將來的目標 120
小結 120
第14章 智能客戶端 122
小結 128
第四部分 系統建模過程 129
第15章 簡述 130
第16章 用例模型——系統需求的獲取 131
第17章 分析模型——開發者的視野 135
第18章 系統設計——實現方案 141
第一部分
綜述
本書會討論什么內容
從軟件工程說起。提起這個概念,往往令人想起CMM、RUP、印度模式等。管理的因素,在軟件開發過程中起著非常重要的作用,然而,軟件工程并 非只指軟件開發的管理工作,而是一個范圍很廣的綜合性學科。在軟件工程中,大約一半的內容是專業性很強的,涉及到軟件分析、設計甚至編碼的技術。所謂的結 構化、面向對象,都在軟件工程的范疇內?!败浖こ谭秶鷺O為廣泛。軟件工程的某些方面屬于數學或計算機科學,其他方面可歸入經濟學、管理學或心理學中。”
軟件業一直在探討,如何使軟件實現如同傳統產業一樣的大規模生產。軟件工程的提出,便是為了實現這個愿望。然而,雖然軟件工程至今已經有了很大的發展,軟件的大規模工業化生產仍然沒有實現。原因何在?
從軟件的本質屬性來說,軟件的復雜性是軟件的本質屬性,在這個屬性沒有改變之前,軟件便不會實現同傳統產業一樣的工廠化生產。
從軟件生產的介質來說,傳統產業生產都是有形的物質產品,人的生產活動都受制于生產資料這些物質介質;然而,軟件生產的介質,卻是無形的人類的 思維。物質資料的生產,受制于物質本身的屬性,不容易為人類的思維所左右,并且容易被大量復制,這使得工業化大生成為可能。而人類的思維,卻是如此的容易 變化,更關鍵的是不能被復制,甚至同一個人,不同時期思維的復制都不可能,這使得軟件這個純粹依賴人的思維活動的生產實現大規模工業化生產是如此的困難。 實際上,不僅僅是軟件產業,凡是主要生產介質是人本身的活動的產業,都很難實現工業化生產,如咨詢、演藝等。
從生產過程來看,對于傳統產業來說,產品的設計和生產是分開的。在設計階段,主要的工作是人的思維,因此,在這個階段,同軟件一樣,不是批量生 產的。而在生產階段,主要的對象便是物質資料,并且一切標準已經制定,只需要在流水線上大量復制。對于傳統產業來說,設計和生產的界限是如此的明確,并 且,生產和設計的比重是如此的懸殊。然而,對于軟件產業來說,軟件的生產過程便是設計的過程,純粹的生產過程幾乎不存在(或許,光盤的復制算是),這使得 軟件的生產形態同傳統產業必然存在區別。
對于軟件的開發過程來說,從業務模型、需求分析、系統架構、系統分析和設計、到最后代碼實現,越往前,抽象層次越高,可控性越小,越往后,越接 近實際,可控性越大,因此,在軟件開發中,核心團隊的作用是如此巨大,一個軟件產品的成敗,核心團隊的核心人員的作用在很大程度上是決定性的。對于軟件開 發來說,如果軟件開發要實現工業化生產,必定是從后向前推進,從編碼開始。印度模式或許給出了這么一個例子。
因此,我們在軟件工程的路上,只是在不斷的向工程化的目標邁進,但是,要達到這個目標,可能會花很長的時間。技術上的每一次進步,都使我們向這 個目標邁進了一步。在軟件工程的發展過程中,技術進步起了非常大,甚至可以說是決定性的作用。隨著采用的技術的不同,所采用的管理方法也在不斷變化。軟件 工程技術的很多方面,也是為管理做準備的。優秀的軟件開發技術的采用,能夠彌補我們在工程化方面的不足,從而使得軟件開發更加可控,軟件質量更加有保障。
本書不準備討論軟件工程過程的問題,而只是對軟件工程中軟件技術的一個方面——系統框架設計,做一些探討。
現在,很多開發人員都已經意識到這很重要的一點,那就是,在開發一個應用軟件系統的時候,一個好的系統框架是非常重要的。從底層開始構建應用程序,是一件吃力不討好的事情,而沒有框架的應用程序,則很難想象會是一個好的應用程序。
除了對于開發的直接幫助,一個好的框架對于公司的知識管理也是非常有意義的。想象一下,我們經常在討論,現在是一個知識經濟的時代,尤其對于軟件公司來說,知識(擁有這些知識的員工)就是公司最大的財富。那么,怎么來進行有效的知識管理呢?
首先,應當明確,知識管理,一個重要的目的,就是要把員工對公司最重要的知識沉淀下來。公司的每個員工頭腦里都有很多的知識,這些知識對于員工 來說是很重要的,但是其重要性同公司并不是完全一致的。某些知識,對于某個員工來說是最重要的,但是對于公司可能并不需要。知識管理需要做的,是把員工對 公司最重要的知識累積起來。
其次,知識管理必須有一個載體。如果知識管理沒有載體,那么,公司的知識就存在于員工的頭腦之中,一旦這個員工離職,那么,知識也就離去了,沒 有辦法沉淀。如果只是把公司做過的項目的文檔作為載體,那么,這個載體就過于零碎了。實際上,如果公司有一個統一的框架,那么,這個框架就是一個很好的知 識管理的載體。因為,這個框架,必定是集中了公司所有軟件項目的共同點的,集中了對于公司最重要的知識的精華,能夠為公司所有的項目服務。另外,隨著框架 的不斷被使用,框架本身也會隨之升級優化。對于一個新成員的加入,他只要理解掌握了這個框架,就可以很好的融入團隊中來;而人員的離去,也已經把自己對公 司最重要的知識留在了這個框架中??梢哉f,在這里,框架承擔了一個知識管理平臺的作用。一個最好的例子就是微軟的Windows。這是微軟所有知識的最集 中的平臺。
軟件,從本質上來說,就是現實世界在計算機中的模擬。在考慮應用軟件系統架構的時候,實際上,考慮的問題主要在于:處理什么?怎么處理?如何使用?因此,應用軟件系統,需要關注的方面,概括起來,主要包括以下三個大類:
處理的對象,也就是數據。
處理的方式,也就是我們的系統如何來處理系統的邏輯。
如何進行交互,這個交互包括用戶(使用者),以及外部系統。
在應用軟件系統中,數據是處理的基本對象,程序總是以一定的數據結構來表現數據,并且,在使用面向對象語言開發的系統中,數據總是以類和對象的 形式表現出來。另外一方面,數據總是需要存儲,對于大部分應用軟件系統來說,通常會采用關系型數據庫來保存數據。這樣,由于數據在程序和數據庫中表現格式 的不一致,就必然要求在兩者之間進行映射。這個映射,在面向對象設計語言和關系型數據庫之間,通常稱為對象/關系型映射,即O/R Mapping。
目前,在O/R Mapping部分,在Java平臺下,已經有多種可以選擇的方案,例如J2EE架構中的Entity Bean,輕量級的JDO,以及開源項目的Hibernate等,由于微軟的.Net框架推出時間不長,成熟的O/R Mapping框架并不多見。O/R Mapping框架的選擇或者設計是構建應用軟件系統的最基本的工作。本書將討論構建O/R Mapping框架的一些基本理論、概念和方法。
系統的業務邏輯處理,是應用軟件系統的核心部分,如何合理的構建業務邏輯、如何提供業務邏輯層的服務,以及表現層如何訪問業務邏輯提供的功能, 也是應用軟件系統需要重點關注的問題。在這個方面,業界已經發展了很多可供選擇的范式,如契約式設計、SOA架構(面向服務的架構)等。這些方法指明了設 計的方向,同時也需要我們在實際開發中加以應用。
在業務邏輯確定后,隨后而來的問題就是,如何向客戶端來提供業務邏輯服務,或者說,客戶端如何訪問這些服務。在多層應用軟件系統中,客戶端和業 務邏輯在物理上可能存在于不同的機器上,也可能存在于同一臺機器,但至少,在邏輯上,是存在于兩個不同部分,這就涉及到一個問題:這兩個層之間如何進行通 信?還會涉及到遠程過程調用的問題。
當然,現在我們已經有多種技術來遠程過程調用,包括Webservice、.Net Remoting、Corba、甚至EJB等。如此多的實現技術,帶來的很大的靈活性,但同時也帶來了問題,其中一個就是,有多少種服務端技術,就得有多 少種相應的客戶端訪問技術。甚至,在某些分布式應用系統中,應用邏輯使用不同的技術開發,存在于不同的機器上,有的存在于客戶機本機,有的使用.Net Remoting開發,存在于局域網內,有的使用因特網上的Web Service,有的時候,我們希望相同的業務邏輯能夠支持不同的客戶端。
在這種情況下,我們需要一個一致的服務訪問編程模型,以統合不同的服務訪問模式,簡化系統的開發和部署。一個統一的遠程過程調用框架的前景是如 此的誘人,以至于每一種方法都試圖一統天下,但出于種種原因,最終都沒有一家能夠做到,最新的Web Service就力圖做到這一點。實際上,每一種方法的出現,最終都會帶來一個副作用,那就是,可供選擇的多了一點,混亂也就又多了一點。在實際的開發過 程中,我們也需要一個統一的訪問方式來解決這個問題。本書將討論一些可用的方案。
為了更加清晰的進行表述,文章會附加一些程序代碼。因為在講到具體的技術的時候,本書會對各種可用的技術進行比較,因此,本書的代碼可能會使用 不同的語言,通常是Java和C#,不過,在給出代碼的時候,一般都會指明所用的語言。在大部分情況下,如果不說明具體的語言,那么就是C#(因為我比較 喜歡這門語言)。因為Java和C#的語法是如此的相像,我想,對有經驗的程序員來說,這應該不會造成閱讀上的麻煩。
系統的分層結構
2
.1.簡述
我們在解決一個復雜的問題的時候,通常使用的一個技巧就是分解,把復雜的問題分解成為若干個簡單的問題,逐步地、分別地解決這幾個小問題,最后 就把整個問題解決掉。在設計一個復雜的軟件系統的時候,同樣的,為了簡化問題,我們也通常使用的一個技術就是分層,每個層完成自身的功能,最后,所有的層 整合起來構成一個完整的系統。
分層是計算機技術中的常用方法,一個典型的例子就是TCP/IP技術的OSI七層模型。在應用軟件開發中,典型的就是N層應用軟件模型。N層的應用軟件系統,由于其眾多的優點,已經成為典型的軟件系統架構,也已經為廣大開發人員所熟知。
在一個典型的三層應用軟件系統中,應用系統通常被劃分成以下三個層次:數據庫層、應用服務層和用戶界面層。如下圖(圖2.1)所示:
圖2.1
其中,應用服務層集中了系統的業務邏輯的處理,因此,可以說是應用軟件系統中的核心部分。軟件系統的健壯性、靈活性、可重用性、可升級性和可維護性,在很大程度上取決于應用服務層的設計。因此,如何構建一個良好架構的應用服務層,是應用軟件開發者需要著重解決的問題。
為了使應用服務層的設計達到最好的效果,我們通常還需要對應用服務層作進一步的職能分析和層次細分。很多開發者在構建應用服務層的時候,把數據 庫操縱、業務邏輯處理甚至界面顯示夾雜在一起,或者,把業務邏輯處理等同于數據庫操縱,等等,這些,都是有缺陷的做法。我們將就在這個方面進行設計時可采 用的方案進行一些探討。
在一個分布式應用系統中,整個系統會部署在不同的物理設備上,如上面所示的三層體系,用戶界面和應用服務器可能在不同的設備上,這就涉及到不同 機器之間的通信問題,也就是層間的通信和交互問題。我們已經有了很多可以用于分布式遠程訪問的技術,如CORBA,在Java平臺上,我們還有Java RMI、EJB,在Windows平臺上,從DCOM到COM+,再到.Net下的Web Service和.Net Remoting等。如何選用合適的遠程訪問技術,也是我們在系統框架中需要考慮的問題。[6]
為了使討論更具有針對性,本文也會討論一些比較流行的系統架構,例如J2EE架構,以及JDO,然后,我們會討論Websharp在這個方面的一些設計理念。
2
.2.設計的原則和評判標準
同軟件工程的原則一樣,應用服務層的設計,必須遵循的最重要的原則就是高內聚和低耦合[7]。軟件分層的本來目的,就是提高軟件的可維護性 和可重用性,而高內聚和低耦合正是達成這一目標必須遵循的原則。盡量降低系統各個部分之間的耦合度,是應用服務層設計中需要重點考慮的問題。
內聚和耦合,包含了橫向和縱向的關系。功能內聚和數據耦合,是我們需要達成的目標。橫向的內聚和耦合,通常體現在系統的各個模塊、類之間的關系,而縱向的耦合,體現在系統的各個層次之間的關系。
系統的框架,通常包含了一系列規范、約定和支撐類庫、服務。
對于如何判斷一個軟件的系統框架的優劣,筆者認為,可以從以下幾個方面來評判:
◆ 系統的內聚和耦合度
這是保證一個系統的架構是否符合軟件工程原則的首要標準。
◆ 層次的清晰和簡潔性
系統每個部分完成功能和目標必須是明確的,同樣的功能,應該只在一個地方實現。如果某個功能可以在系統不同的地方實現,那么,將會給后來的開發和維護帶來問題。
系統應該簡單明了,過于復雜的系統架構,會帶來不必要的成本和維護難度。在盡可能的情況下,一個部分應該完成一個單獨并且完整的功能。
◆ 易于實現性
如果系統架構的實現非常困難,甚至超出團隊現有的技術能力,那么,團隊不得不花很多的精力用于架構的開發,這對于整個項目來說,可能會得不償失。簡單就是美。
◆ 可升級和可擴充性
一個系統框架,受設計時技術條件的限制,或者設計者本人對系統認識的局限,可能不會考慮到今后所有的變化。但是,系統必須為將來可能的變化做好準備,能夠在今后,在目前已有的基礎上進行演進,但不會影響原有的應用。接口技術,是在這個方面普遍應用的技巧。
◆ 是否有利于團隊合作開發
一個好的系統架構,不僅僅只是從技術的角度來看,而且,它還應該適用于團隊開發模型,可以方便一個開發團隊中各個不同角色的互相協作。例如,將Web頁面和業務邏輯組件分開,可是使頁面設計人員和程序員的工作分開來同步進行而不會互相影響。
◆ 性能
性能對于軟件系統來說是很重要的,但是,有的時候,為了能讓系統得到更大的靈活性,可能不得不在性能和其他方面取得平衡。另外一個方面,由于硬件技術的飛速發展和價格的下降,性能的問題往往可以通過使用使用更好的硬件來獲得提升。
2
.3.應用服務層的內容
應用服務層,通常也被稱為業務邏輯層,因為這一層,是應用軟件系統業務邏輯處理集中的部分。然而,我將這一層稱為應用服務層,而不稱業務邏輯層,因為,這一層需要處理的不僅僅是業務邏輯,還包含了其他方面的內容。
從完整的角度來說,應用服務層需要處理以下內容:
◆ 數據的表示方式
數據,是軟件處理的對象。從某種程度上來說,"軟件,就是數據結構加算法"的說法,是有一定意義的。在面向對象的系統中,數據是用類來表示 的,代表了現實世界實體對象在軟件系統中的抽象??紤]所謂的MVC模式,這個部分的類屬于M--實體類的范疇。由于應用軟件通常會使用數據庫,數據庫中的 數據,可以看成是對象的持久化保存。由于數據庫一般是關系型的,因此,這個部分,還需要考慮類(對象)同關系型數據的映射,即通常所說的O-R MAP問題。
◆ 數據的存取方式
如同上述所說,軟件系統處理的實體對象數據需要持久化保存數據庫中,因此,我們必須處理系統同數據庫的交互,以及數據的存取和轉換方式的問題。
◆ 業務邏輯的組織方式
在面向對象的系統中,業務邏輯表現為對象之間的交互。有了上述的實體對象,以及對象的保存策略,就可以將這些對象組合起來,編寫我們的業務 邏輯處理程序。在業務邏輯的處理中,必須保證處理的正確性和完整性,這將會涉及到事務處理。通常,我們也會把業務邏輯封裝成組件的形式,以得到最大的可重 用性。
◆ 業務服務的提供方式
在我們完成系統的功能后,如何向客戶提供服務,是我們需要考慮的問題。這里的客戶,不僅僅是指軟件的使用者,也包括調用的界面、其他程序 等。例如,在一個基于Web的ASP.Net或JSP系統中,業務邏輯功能的客戶便是這些ASP.Net頁面或JSP頁面。業務邏輯組件應該通過什么方 式,直接的,或間接的,向這些客戶提供服務,是這一層需要完成的任務。
◆ 層的部署和層間交互
對于一個多層的應用軟件系統來說,尤其是大型的應用軟件系統,通常需要把不同的部分部署在不同的邏輯或物理設備上。特別是一些基于Web的 應用軟件系統,其部署工作將涉及到Web服務器、組件服務器、數據庫服務器等不同的服務設備。在進行應用軟件架構的設計的時候,必須考慮各種不同的部署方 案。 當系統需要進行分布式訪問的時候,如何統一和簡化分布式系統的開發,便成了系統框架需要考慮的內容。
綜上所述,一個完整的基于Web的應用軟件系統,其架構可以用圖2.2來表示(Websharp的應用軟件系統架構):
圖2.2
對于以上各個方面來說,每個問題都可以有很多種策略和方案,但是,在一個系統中,應該盡可能的統一這些策略和方案。也就是說,在一個系統,或者 一個項目中,應該統一每個解決每個問題所采用的方法。軟件的開發方法是靈活的,可以用不同的方法解決相同的問題,這會誘使開發人員采用他們認為能夠表現自 己的方法,但是,從整個系統來看,這將會是災難性的。我們應該盡可能統一,就是,采用統一的數據表示方式、統一的數據存取方式、統一的業務邏輯處理方式 等。
下面,將就這些部分的設計策略和可用方案進行一些比較詳細的論述。
2
.4.數據實體的表示
應用軟件系統,從本質上來說,是計算機對現實世界的模擬?,F實世界中的實體對象,在軟件系統中,表現為需要處理的數據。在面向對象的系統中,這是通過“類"和”對象"來表示的。
參考著名的“MVC”模式[8],類可以分成實體類(M)、控制類(C)、和邊界類(V),分別代表了實體對象、控制和界面顯示。系統中需要處理的數據,在面向對象的系統中,屬于實體類部分。
在考慮數據實體層的設計策略的時候,需要把握以下要點:
◆ 一致的數據表示方式。在一個系統中,數據的表示方式必須盡可能統一,同時,在處理單個數據和多個數據的時候,處理方式盡可能一致。
◆ 因為數據通常是需要存儲到數據庫中,因此,良好的映射方法是必需的。
◆ 處理好對象的粒度,即所謂的粗粒度對象、細粒度對象。
一般例子
考慮一個現實的例子,一個倉庫中的產品(Product),在系統中可以使用如下定義:
public class Product
{
public string Name; //名稱
public decimal Price;//價格
public int Count;//數量
}
可以按照如下方法使用Product類:
Product p=new Product();
//……處理Product
|
這是一個包含了三個屬性的Product類的定義。為了便于說明,在這里,我們盡量將問題簡化了。
又例如,一張入庫單可以使用如下定義:
public class Form
{
public string ID; //入庫單編號
public DateTime AddTime; //入庫時間
public FormDetail[] FormDetails; //入庫單明細
}
public class FormDetail
{
public Product InProduct; //入庫產品
public int Count; //入庫數量
}
|
對于處理單個對象,通常采用上述的方法,但是,當我們需要處理相同類的一組對象,也就是處理一個對象集合的時候,就會有一些小小的麻煩。
如前所述,我們希望在處理單個對象和對象集合的時候,處理的方式盡量統一,這對于軟件開發的意義是很大的。常用的處理對象集合的方法有:
◆數組表示的方法
例如,上面的例子中當一張入庫單包含多條入庫單明細的時候采用的方法。為了靈活性,也可以使用容器來,如Java中的Vector或C#的 ArrayList(C#)。只是,在處理對象的時候,需要一個類型轉換的操作。這個問題,在支持泛型的語言中不會存在,如使用C++的標準庫的容器類。
ObjectCollection方法。
這個方法同上面的方法類似,不同之處在于,為每個實體類設計一個Collection類。例如,可以為FormDetail設計一個FormDetailsCollection類(C#):
public class FormDetailsCollection: ArrayList
{
public void Add(FormDetail detail)
{
base.Add(detail);
}
public new FormDetail this[int nIndex]
{
get
{
return (FormDetail)base[nIndex];
}
}
}
|
這么做的好處在于,在操作集合中的對象時,不必進行類型轉換的操作。
◆數據集的表示方法。
采用這種方法,通常是直接把從數據庫查詢中獲取的數據集(Recordset)作為數據處理對象。這種方法在ASP應用程序中是非常常見的做法。這種做法簡單,初學者很容易掌握,但是他不是一種面向對象的方法,弊病也很多。
EJB的方法
在J2EE體系中,對實體對象的處理的典型方法是Entity Bean。J2EE中使用Entity Bean來表示數據,以及封裝數據的持久化儲存(同數據庫的交互)。由于Entity Bean比較消耗資源,而且采用的是遠程調用的方式來訪問,因此,在需要傳遞大量數據,或者在不同的層次之間傳遞數據的時候,往往還會采用一些諸如"值對 象"(Value Object)的設計模式來提升性能。關于J2EE中的設計模式的更多內容,可以參考《J2EE核心模式》一書。 [9]
JDO的方法
相對于J2EE這個昂貴的方法來說,JDO提供了一個相對"輕量級"的方案。在JDO中,你可以采用一般的做法,編寫實體類,然后,通過一 些強化器對這些類進行強化,以使其符合JDO的規范,最后,你可以通過PersistenceManager來實現對象的持久化儲存。 [10]
無論是EJB還是JDO,在同數據庫進行映射的時候,都選用了XML配置文件的方式。這是一種靈活的方式。由于XML強大的表達能力,我們 可以很好的用它來描述代碼中的實體類和數據庫之間的映射關系,并且,不用在代碼中進行硬編碼,這樣,在情況發生變化的時候,有可能只需要修改配置文件,而 不用去修改程序的源代碼。關于EJB和JDO的配置文件的更多的信息,各位可以參考相關的文檔,這里不再贅述了。
然而,使用XML配置文件的方式并不是唯一的方法,在微軟提供的一些案例中,如Duwamish示例[11],就沒有采用這種方式。至于開發人員在開發過程中具體采用哪種方式,是需要根據具體情況進行權衡和取舍的。
Websharp的方法
Websharp在數據的表現上,充分利用了.Net Framework類庫中DataSet和特性(Attribute)的功能。我們設計了一個EntityData類,這個類繼承了DataSet,并增加了一些屬性和方法。
在Websharp中,當表示一個實體類的時候,需要定義一個抽象類,這個抽象類繼承PersistenceCapable。例如,一個Schdule類可以表示如下:
[TableMap("Schdule","GUID")]
[WebsharpEntityInclude(typeof(Schdule))]
public abstract class Schdule : PersistenceCapable
{
[ColumnMap("GUID",DbType.String,"")]
public abstract string GUID{get;set;}
[ColumnMap("UserID",DbType.String,"")]
public abstract string UserID{get;set;}
[ColumnMap("StartTime",DbType.DateTime)]
public abstract DateTime StartTime{get;set;}
[ColumnMap("EndTime",DbType.DateTime)]
public abstract DateTime EndTime{get;set;}
[ColumnMap("Title",DbType.String,"")]
public abstract string Title{get;set;}
[ColumnMap("Description",DbType.String,"")]
public abstract string Description{get;set;}
[ColumnMap("RemidTime",DbType.DateTime)]
public abstract DateTime RemidTime{get;set;}
[ColumnMap("AddTime",DbType.DateTime)]
public abstract DateTime AddTime{get;set;}
[ColumnMap("Status",DbType.Int16,0)]
public abstract short Status{get;set;}
}
|
類的TableMap 特性指明了同Schdule實體類相映射的數據庫表,以及關鍵字,ColumnMap特性指明了同某個屬性相映射的數據庫表字段,以及數據類型和默認值。
在實際的應用中,定義了這樣一個Schdule抽象類后,要獲取一個實體對象,因為Schdule類是抽象的,所以你不可以直接使用new操作來初始化Schdule對象,應當通過如下方式取得:
Schdule schdule = EntityManager.CreateObject(typeof(Schdule)) as Schdule;
|
EntityManager會即時編譯出一個Schdule的實現類,并且返回一個對象。
在這種方式下,實體類同數據庫表的映射是通過Attribute來實現的。
可以使用另外一種方法來表示一個實體類。在這種方式下,需要編寫一個XML映射文件,然后,可以使用如下方式取得一個實體對象:
EntityData schdule =EntityManager.GetEntityData("Schdule");
|
然后,可以通過如下方式來訪問這個對象的屬性:
string Title = schdule["Title"]
|
可以看到,這種方式同傳統的方式有點不同。在這種方式下,數據的表現形式只有一個,那就是EntityData。其好處是明顯的,不用為每個實 體都單獨編寫一個類,能夠大大減少代碼的編寫量。其缺點也很明顯,那就是不能利用編譯器類型檢測的功能,如果在調用對象的屬性的時候,寫錯了屬性的名稱, 就可能出錯,這需要更加仔細的測試工作。但是,這個問題可以通過工具生成代碼來解決。
2
.5.數據的存取方式
數據存取的目的,是持久化保存對象,以備后來的使用,如查詢、修改、統計分析等。存取的對象,可以是數據庫、普通文件、XML甚至其他任何方 式,只要保證數據能夠長久保存,并且,不會受斷電、系統重起等因素的影響。在這個部分,最理想的狀況,自然是能夠支持除了數據庫以外的各種類型的存取方 式,或者,至少留有接口,能夠比較方便的擴充。
因為數據庫是最常用,也是最有效的數據存儲方法,因此,支持數據庫存儲是最首先必須支持的。在不同的平臺下,有不同的數據庫訪問的手段。例如, 在Java平臺下,有JDBC,在Windows平臺下,可以使用ADO、ADO.Net等。但是,這些手段還比較接近底層,在實際操縱數據庫的時候,需 要編寫大量的代碼,并且,我們還需要通過手工的方式來完成將程序中的面向對象的數據存儲到關系型數據庫的工作。這么做,自然編程的效率不高,并且非常容易 出錯。但是,不可否認,這也是一種可以選用的方式。
從另外一個方面來看,由于我們前面已經解決了數據的映射問題,因此,在數據的存取方面是非常有規律的,我們完全可以讓這個工作通過框架來執行。 這樣,我們一方面可以簡化很多同數據庫交互方面的代碼編寫工作量,能夠減少出現Bug的幾率,另一方面,由于框架封裝了不同數據庫之間的差異,使得我們在 編寫程序的時候,不用考慮不同數據庫之間的差異,而將這個工作交給框架去做,實現軟件的后臺數據庫無關性。
在這個部分,以下兩個部分的類會顯得特別重要:
◆對象--關系映射的分析類,能夠通過既定的方案完成對象--關系的映射,確定數據存取方案
◆數據庫操縱類:根據映射關系,將數據準確的存儲到數據庫中,并且封裝不同數據庫之間的差異。
這個部分的操作過程,可以用圖(圖2.3)大概的表示如下:
圖2.3
在J2EE中,這個部分比較典型的就是EntityBean中的CMP。由于在BMP中,同數據庫的交互部分需要通過手工編寫代碼的方式來 實現,因此,很難享受到容器帶來的便利,只是由于EJB2.0以前的標準,CMP的功能,包括映射能力、實體關系模式等方面的功能比較弱,所以,在很多時 候,我們不得不使用BMP。現在,EJB2.0,在這個方面的功能已經非常強大了,我們完全可以享受容器帶來的便利,而將大部分精力放在實現更加復雜的業 務邏輯方面了。
在JDO中,您同樣可以通過PersistenceManager來實現同樣的目標,例如,您想把一個Customer對象保存到數據庫中,可以采用類似于下面的代碼:
Schdule schdule=new Schdule(……);
PersistenceManager PM=PMFactory.initialize(……);
Pm.persist(schdule);
|
代碼同樣非常簡明和直觀,沒有一大堆數據庫操縱的代碼,也不容易發生差錯。
Websharp的方案
同JDO類似,Websharp定義了PersistenceManager接口,這個接口的定義在后面的章節中會給出,這里,我們先看看其使用方式。
當我們有了某個實體對象后,需要保存到數據庫中的時候,我們可以使用下面的代碼來實現:
public bool AddSchdule(Schdule schdule)
{
PersistenceManager pm =
PersistenceManagerFactory.Instance().CreatePersistenceManager();
try
{
pm.PersistNewObject(schdule);
return true;
}
catch
{
return false;
}
finally
{
pm.Close();
}
}
|
在這里,我們不需要關心具體的數據庫版本,框架會封裝不同數據庫之間的差異,保證數據可以正確的存儲到不同的數據庫中。
在這個部分,另外需要注意的是,為了保證數據存儲的完整性,應當考慮事務處理的功能。J2EE、JDO和Websharp都支持在數據存儲的時候使用事務處理。
在Websharp中,通過Transaction接口,提供了基本的事務處理能力。上面的代碼,如果需要使用事務處理,則可以修正如下:
public bool AddSchdule(Schdule schdule)
{
if(!CheckSchdule(schdule))
return false;
PersistenceManager pm =
PersistenceManagerFactory.Instance().CreatePersistenceManager();
Transaction trans = pm.CurrentTransaction;
trans.Begin();
try
{
pm.PersistNewObject(schdule);
trans.Commit();
return true;
}
catch
{
trans.Rollback();
return false;
}
finally
{
pm.Close();
}
}
|
關于事務處理的Transaction接口的更多內容,在后面的章節中會詳細說明。
2
.6.業務邏輯的處理
有了上面的工作,我們就可以把這些對象組合起來,編寫我們的業務邏輯。在面向對象的系統中,業務邏輯表現為對象之間的交互。在一些簡單的系統中,沒有復雜的業務邏輯,只是一些數據的維護工作,那么,有了上面兩個部分的工作,我們實際上可能已經忘成了大部分的工作。
在這個部分,由于不同系統之間業務邏輯千差萬別,基本上沒有辦法提供統一的模式。但是,應當注意的是,在同一個系統中,采用基本一致的策略是非常必要的,這有助于消除項目內部的不一致性,使項目更加可控。甚至于,這些策略可以擴展成公司部分、甚至所有項目的策略。
值得指出的是,很多人在這個部分操縱數據庫,把業務邏輯處理等同于數據庫操作,這是不可取的。在業務邏輯處理中,處理的應該是對象,而不是直接同數據庫打交道,這樣,才能獲得更好的系統結構。
在業務邏輯處理部分,由框架提供一些支撐的服務是非常必要的。這其中,最重要的一點就是事務的處理。業務邏輯的處理過程,會涉及到多個對象之間的交互,以及多次同數據庫的交互。為了保證處理過程的完整性,必須使用事務處理的方法??蚣鼙仨氈С质聞仗幚?。
事務處理的功能,基本上有兩種選擇:使用基于數據庫連接的事務、使用外部事物處理服務[12]。
使用基于數據庫連接的事務,事務處理的性能相對比較高,但是,當系統涉及到多個數據庫之間的交互時,基于數據庫連接的事務便無能為力了。而使用專用的事務處理服務,能夠適應更多的情況,并且,有測試表明,隨著數據處理量的上升,兩者之間的性能差異會逐漸減小。
在J2EE中,容器提供了事務處理的能力。在.Net平臺上,事務處理是通過Windows COM+服務來提供的。在Websharp中,如上面所講,通過Transaction接口,提供了基本的事務處理能力,能夠滿足大部分事務處理的要求。 當Websharp提供的事務處理能力不能滿足需求的時候,可以使用EnterpriseService。
下面是一個簡單的例子:
public bool AddSchdule(Schdule schdule,string[] otherPeoples)
{
if(!CheckSchdule(schdule))
return false;
PersistenceManager pm =
PersistenceManagerFactory.Instance().CreatePersistenceManager();
Transaction trans = pm.CurrentTransaction;
trans.Begin();
try
{
pm.PersistNewObject(schdule);
foreach(string otherPeople in otherPeoples)
{
Schdule s = EntityManager.CreateObject(typeof(Schdule)) as Schdule;
s.GUID = Guid.NewGuid().ToString();
s.UserID = otherPeople;
s.StartTime = schdule.StartTime;
s.EndTime = schdule.StartTime;
s.Title = schdule.Title;
s.Description = schdule.Description;
s.RemidTime = schdule.RemidTime;
s.AddTime = DateTime.Now;
s.Status = 0;
pm.PersistNewObject(s);
}
trans.Commit();
return true;
}
catch
{
trans.Rollback();
return false;
}
finally
{
pm.Close();
}
}
|
在業務邏輯這一層,另外一個需要關注的問題是所謂的AOP。關于AOP的內容,我們會在下面的章節中再討論。
2
.7.業務服務的提供
業務外觀層(Business Facade)的目的,是隔離系統功能的提供者和使用者,更明確地說,是隔離業務邏輯的軟件的用戶界面(可以參見Facade設計模式)。這一層沒有任何 需要處理的邏輯,只是作為后臺邏輯處理和前端用戶界面的緩沖區,以達到如下目的
◆將用戶界面和系統業務邏輯處理分開,這樣,當業務邏輯發生變化時,不用修改客戶端程序,是一種支持變化的設計方法。
◆使同一個業務邏輯能夠處理不同的客戶端請求。例如,可以將Facade設計成Web Service,這樣,可以同時為傳統的WinForm客戶端程序、Web程序以及其他外部系統提供服務,而使用相同的應用服務層,同時,也可以實現系統 的分布式部署。關于如何做到這一點,可以參見Ioffice的Demo程序。
◆作為系統不同模塊之間的調用接口。一個系統通常會包含很多模塊,這些模塊相對獨立,又可能互相調用。為了減少各個不同部分之間的耦合度,必須采用一定的設計方法,Facade設計模式就是非常有效的一種,也是業務外觀層的基礎。
◆有利于項目團隊的分工協作。業務外觀層作為一個訪問接口,將界面設計人員和邏輯設計人員分開,使得系統的開發可以實現縱向的分工,不同的開發人員可以關注自己的領域而不會受到干擾。
業務外觀層的代碼框架,在系統分析和設計完成后就可以完成,他需要提供的方法,就相當于在界面設計人員和邏輯設計人員之間簽訂了一個協議, 他雖然沒有實現任何邏輯,但是,他的引入,能使系統的開發更加有條理,更加簡明。套用《設計模式》上的一句話,就是,“任何問題,都可以通過引入一個中間 層來得到簡化”。
2
.8.層的部署和層間交互
對于一個多層的應用軟件系統來說,尤其是大型的應用軟件系統,通常需要把不同的部分部署在不同的邏輯或物理設備上。特別是一些基于Web的應用軟件系統, 其部署工作將涉及到Web服務器、組件服務器、數據庫服務器等不同的服務設備。在進行應用軟件架構的設計的時候,必須考慮各種不同的部署方案。
已經有了很多可以用于遠程訪問的服務,如此多的實現技術,帶來的很大的靈活性,但同時也帶來了文題,其中一個就是,有多少種服務端技術,就得有 多少種相應的客戶端訪問技術。甚至,在某些分布式應用系統中,應用邏輯使用不同的技術開發,存在于不同的機器上,有的存在于客戶機本機,有的使用.Net Remoting開發,存在于局域網內,有的使用因特網上的Web Service,有的時候,我們希望相同的業務邏輯能夠支持不同的客戶端。
在這種情況下,我們需要一個一致的服務訪問編程模型,以統合不同的服務訪問模式,簡化系統的開發和部署。Websharp中的Service Locator提供了這樣一種能力,開發人員只需要定義服務訪問接口,就可以使用一致的方式透明的訪問這些服務,而不用理會這些服務之間的不同點。框架會 自動生成訪問遠程服務需要的代理。
使用WSL,你可以使用類似于如下的代碼來訪問遠程服務,而不用關心遠程服務的種類:
public interface ISecuritySystem
{
bool Login(string userID,string password);
void Logout();
bool IsLogin();
Suser CurrentUser();
}
……
//在需要調用服務的客戶端:
ISecuritySystem ss = ServiceLocator.FindService(
"SecurityService",typeof(ISecuritySystem)) as ISecuritySystem;
|
關于WSL的更多內容,在后面會更加詳細的討論。
2
.9.剪裁和取舍
以上四個層次,對于大型的應用軟件系統來說,是非常必要的。但是,對于一些小型的應用軟件系統,如果完全按照以上的層次來做,可能反而會影響工作效率。因此,針對不同的系統,可以對架構進行一定的剪裁。
數據實體層和實體控制層,是每個應用軟件系統所必需的,顯然無法裁減。對于業務邏輯層和業務外觀層,根據實體情況,可以進行如下裁減:
◆如果系統沒有復雜的業務邏輯,而只是一些數據的操作,或者業務邏輯特別少,那么,可以省略業務邏輯層,而將相關的功能移至實體控制層。
◆如果不考慮多種客戶端的情況,也不考慮分布式部署的問題,系統的模塊又很少,不會產生模塊間緊耦合的情況,那么,可以不使用業務外觀層,而讓用戶界面程序直接訪問業務功能。
在上面的論述中,對于每個層次,都說明了可以選擇的多種方案,每一種方案都有他的優點和缺點,在具體開發的過程中,需要根據具體情況加以取舍。
2
.10.小結
應用軟件系統架構,是軟件工程的重要組成部分。設計一個好的框架,其目的很明確,那就是,在目前還沒有"銀彈"之前,盡最大的可能,提高軟件開發的效率和軟件質量,把不必要的工作和容易出錯的工作,交給框架去處理。
應用服務層,在軟件系統中,是一個非常復雜的部分,乍看之下,沒有任何規律可行,給人無從下手的感覺。我們的目標,就是盡量化無規律為有規 律,把有規律的東西提取出來,形成規范,從而減少今后的開發工作量。其方法,就是對系統進行合理的分層,這樣,系統的層次清晰了,每個層次完成的功能就比 較單一,就意味著每個層次的都相對更有規律可循,這樣,我們就可以把這些有規律的東西交給框架去執行,或者,開發一個輔助工具,來完成這部分的代碼編寫工 作。Websharp就提供了這樣一個代碼自動生成的工具。這個工具被設計成Visual Studio.Net集成開發環境的插件,在實際開發過程中,能夠提供很多便利。這是系統層次清晰帶來的另外一個好處。
對于一個軟件公司來說,統一的系統框架的意義不僅僅在于軟件開發的本身。一個統一的系統框架,也是公司知識管理的重要組成部分。公司如果有一個 或有限個數的明確的軟件框架,那么,這些框架就可以成為凝結公司開發人員經驗、智慧的載體,并且可以在不斷的實踐中加以充實和完善。由于公司的軟件系統的 框架比較統一,那么當某個項目更換或增加開發人員的時候,后來的人也能夠比較容易接手,這對于公司的開發管理是具有非常重要的意義的。
第二部分
應用服務層的設計
數據和對象
3
.1數據的形態
在應用軟件系統中,首先要處理的對一個對象就是數據。應用軟件系統,主要目標就是采集數據、處理數據、分析數據、察看數據。對于軟件,誠如有一句名言所說:“軟件,就是數據結構加算法”。
在軟件中,數據有多種表現形態。
首先,在程序中,數據總是以某種數據結構的方式被表示出來,這種表示,通常被編譯成二進制文件存在于硬盤上,并且在運行時刻在內存中被實例化。
這種數據結構有多種表達的方式,簡單的情況下,他可能只是一個數字,或者一個字符串,用某個變量來描述。例如,為了表述某種商品的價格,可能使用如下的申明來表述這個數據:
double price = 100 ;
|
現實中要處理的數據總是比較復雜的,為了描述一個完整的信息,通常要組合多項簡單的數據,例如,為了描述某種商品的信息,通常需要描述他的名稱、價格、重量等。在傳統的C語言中,可以使用結構來描述:
struct product
{
char* name;
double price;
double weight;
}
|
在面向對象的語言里,類似的數據結構,可以使用類來表述。上面的代碼可以用Java語言表述如下:
public class Product
{
public String name;
public double price;
public double weight;
}
|
可以看出來,實際上兩者的差別是非常小的。
對于更加復雜的數據結構,一個類可能引用到其它的類,例如,上面的Product,可能有一個Size屬性,而這個Size屬性,也有height和width構成,那么,整體的數據結構就可以描述如下:
public class Product
{
public String name;
public double price;
public double weight;
public Size size;
}
public class Size
{
public int height;
public int width;
}
|
數據的另外一種表現形態,就是永久化保存的形態。上面描述的數據的形態,是一種“瞬時”的數據,只有在程序運行的時候才存在于內存中,一旦程序結束,或者數據處理結束,數據就從內存中清楚。在很多情況下,需要把處理的數據保存到磁盤上,這時候,數據就進入永久化保存狀態。
可以有多種保存數據的格式??梢园褦祿4鏋槠胀ㄎ谋疚募娣旁诖疟P上,或者,也可把數據保存在XML文件中。在Java和C#中,也都提供了這樣一種能力,就是可以把對象序列化后保存到磁盤上,然后,在需要的時候,可以反序列化成對象。
雖然有多種持久化保存數據的方案,然而其中使用關系型數據庫來保存數據,無疑是最常用的辦法和最可靠的辦法。這就引伸出一個在面向對象的系統設計中的常見問題:對象/關系型映射(O/R Mapping)。
在考慮O/R Mapping的時候,有兩個概念是經常會接觸的,那就是VO和PO
所謂的VO,就是Value Object,這種對象,只包含了對象的數據,而沒有狀態,或者說,處于瞬時狀態。VO可以用來在層之間傳遞數據
所謂PO,就是Persistent Object,就是持久化保存的對象,這種對象,一般是有狀態的。O/R Mapping框架需要根據PO的狀態,來執行相應的同數據庫的交互。關于PO的狀態,我們在后面再討論
3
.2對象/關系型映射
對象關系型映射,最核心的要完成兩個功能:對象和關系型之間的映射規則,以及兩者之間的相互轉換。
除了這兩個基本的功能,一般的O/R Mapping產品還會加上一些額外的特性和功能,以加強產品的功能,為軟件開發提供更多的方便和提高性能。一些常見的功能,例如緩存。
現在有一些典型的O/R Mapping框架可以參考和使用,比較著名的有EJB中的Entity Bean,JDO,Hibernate等,這些方案都是基于Java的。在Microsoft.Net平臺下,相對來說可供選擇的方案比較少,其中有一個 開放源代碼的方案Websharp,可以從
www.websharp.org
下載。
我們在實際開發中,可以選擇使用已有的解決方案和產品,也可以自己設計自己的O/R Mapping框架。當然,無論采用何種方式,都需要我們對O/R Mapping的基本原理和方法有一個基本的了解。
在支持OO的語言中,繼承是語言的基本特征。O/R Mapping的框架,也需要對繼承做出相應的支持。一般說來,有三種繼承模式:ONE_INHERITANCE_TREE_ONE_TABLE、 ONE_INHERITANCE_PATH_ONE_TABLE和ONE_CLASS_ONE_TABLE。
ONE_INHERITANCE_TREE_ONE_TABLE:
一個繼承樹映射到一個表,即將具有相同父類的所有類都映射到一個數據庫表中,這些類屬性映射的集合組成這個表的列,在這種模式下,只需要對最底層的類進行映射。
如下面一個類結構:
在這個類結構中,父類Parent有兩個子類Child1和Child2,父類有屬性Property1和Property2,Child1新 增了一個屬性Property3,而Child2新增了另外一個屬性Property4,當采用 ONE_INHERITANCE_TREE_ONE_TABLE映射模式的時候,數據庫中只有一張表,結構如下:
其中,Column1和Column2字段分別對應Parent類的Property1和Property2屬性,Column3字段對應 Child1的Property3屬性,而Column4字段對應Child2的Property4屬性。Column3對于Child2和 Column4對于Child1是沒有意義的。
采用這種映射模式,優點是比較簡單,缺點是數據的冗余比較大。這個表要容納所有的子類的字段,很多字段只是對某個類有意義,而對于其他類則沒有意義,是純粹多余的,并且,在繼承樹中新增一個繼承節點的時候,往往導致表的字段的重新設計,這是一件非常麻煩的事情。
ONE_INHERITANCE_PATH_ONE_TABLE:
一個繼承路徑映射到一個表,即將一個類映射到一個數據庫表中,這個類的所有屬性(包括從父類繼承來的)映射的集合組成這個表的列。
在這種模式下,對于上面的類結構,數據庫的結構就是:
其中,表Child1和Child2分別對應于類Child1和Child2。
這種模式是非常常用的,也沒有數據冗余,缺點是在搜索的時候比較麻煩。例如,當我要搜索符合某個條件的Parent對象的時候,需要同時搜索 Child1和Child2表,并且,當在繼承樹中新增一個繼承節點的時候,需要新增一個表,搜索的范圍也必須擴大,原來的程序可能不得不重新設計。
ONE_CLASS_ONE_TABLE:
一個類映射到一個表,即將每個類映射到對應的一個表,這個類的屬性(不包括從父類繼承來的非主鍵屬性)映射組成這個表的列,在這種模式下,具有繼承關系的類被映射到不同的表中,表之間通過主鍵關聯。
在這種模式下,對于上面的類結構,數據庫的結構就是:
表Parent作為Child1和Child2的父表,父子表之間通過主鍵Colimn1關聯。
這種模式是非常容易理解的,因為和類圖非常相像,缺點是在查詢的時候,由于設計到表的關聯,SQL語句會比較復雜,效率也會比較低。這個情況當繼承數的深度增加的時候,會體現的比較明顯。
如果一個類沒有子類和父類,那么采用三種模式中的哪一種都是一樣的效果。
3
.3對象的狀態
為了很好的控制對象,以及在同后臺存儲交互時的行為,通常O/R Mapping框架需要維護PO對象的狀態。在不同的框架中,對對象狀態的定義不盡相同,不過,也都有一些共同點,某些方面可能只是名稱的不同。通常的 O/R Mapping框架都需要以各種方式來直接或間接的處理PO的
發表評論
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

評論