DO、DTO、BO、VO、POJO等各种O浅学(总结)
DO、DTO、BO、VO、POJO等各種O淺學(xué)(總結(jié))
- 有哪些 O
- 關(guān)鍵的 O :DO、DTO、VO、BO
- 整個數(shù)據(jù)流程傳遞
- 區(qū)別用處
- VO與DTO的區(qū)別
- VO與DTO的應(yīng)用
- DTO與DO的區(qū)別
- DTO與DO的應(yīng)用
- DO與PO的區(qū)別
- DO與PO的應(yīng)用
- 領(lǐng)域模型命名規(guī)約:
有哪些 O
- DO( Data Object):與數(shù)據(jù)庫表結(jié)構(gòu)一一對應(yīng),通過 DAO 層向上傳輸數(shù)據(jù)源對象。
- PO(Persistant Object):持久對象,一個 PO 的數(shù)據(jù)結(jié)構(gòu)對應(yīng)著庫中表的結(jié)構(gòu),表中的一條記錄就是一個 PO 對象
- DTO( Data Transfer Object):數(shù)據(jù)傳輸對象,Service 或 Manager 向外傳輸?shù)膶ο蟆?/li>
- BO( Business Object):業(yè)務(wù)對象。由 Service 層輸出的封裝業(yè)務(wù)邏輯的對象。
- AO( Application Object):應(yīng)用對象。在 Web 層與 Service 層之間抽象的復(fù)用對象模型,極為貼近展示層,復(fù)用度不高。
- VO( View Object):顯示層對象,通常是 Web 向模板渲染引擎層傳輸?shù)膶ο蟆?/li>
- POJO( Plain Ordinary Java Object):POJO 專指只有 setter/getter/toString 的簡單類,包括 DO/DTO/BO/VO 等。
- DAO(Data Access Objects):數(shù)據(jù)訪問對象,和上面那些 O 不同的是,其功能是用于進行數(shù)據(jù)操作的。通常不會用于描述數(shù)據(jù)實體
阿里Java開發(fā)手冊分層領(lǐng)域模型:
關(guān)鍵的 O :DO、DTO、VO、BO
-
VO (View Object),用于表示一個與前端進行交互的視圖對象,它的作用是把某個指定頁面(或組件)的所有數(shù)據(jù)封裝起來。實際上,這里的 VO 只包含前端需要展示的數(shù)據(jù),對于前端不需要的數(shù)據(jù),比如數(shù)據(jù)創(chuàng)建和修改的時間等字段,出于減少傳輸數(shù)據(jù)量大小和保護數(shù)據(jù)庫結(jié)構(gòu)不外泄的目的,不應(yīng)該在 VO 中體現(xiàn)出來。
-
DTO(Data Transfer Object),用于表示一個數(shù)據(jù)傳輸對象,DTO 通常用于展示層(Controller)和服務(wù)層(Service)之間的數(shù)據(jù)傳輸對象。DTO 與 VO 概念相似,并且通常情況下字段也基本一致。但 DTO 與 VO 又有一些不同,這個不同主要是設(shè)計理念上的,比如 API 服務(wù)需要使用的 DTO 就可能與 VO 存在差異。
-
DO(Data Object) ,持久化對象,它跟持久層(Dao)的數(shù)據(jù)結(jié)構(gòu)形成一一對應(yīng)的映射關(guān)系。如果持久層是關(guān)系型數(shù)據(jù)庫,那么數(shù)據(jù)庫表中的每個字段就對應(yīng)PO的一個屬性,常是entity實體類。
-
BO(Business Object):業(yè)務(wù)對象,就是從現(xiàn)實世界中抽象出來的有形或無形的業(yè)務(wù)實體。
整個數(shù)據(jù)流程傳遞
一般的數(shù)據(jù)傳遞是,前端傳遞VO給接口(Controller),接口將VO轉(zhuǎn)為DTO傳遞給service,service將DTO分解為DO,調(diào)用領(lǐng)域服務(wù)進行調(diào)度,然后逆向轉(zhuǎn)為VO或者其他的返回結(jié)果,傳遞給前臺。
區(qū)別用處
VO與DTO的區(qū)別
大家可能會有個疑問(在筆者參與的項目中,很多程序員也有相同的疑惑):既然DTO是展示層與服務(wù)層之間傳遞數(shù)據(jù)的對象,為什么還需要一個VO呢?對!對于絕大部分的應(yīng)用場景來說,DTO和VO的屬性值基本是一致的,而且他們通常都是POJO,因此沒必要多此一舉。但不要忘記這是實現(xiàn)層面的思維,對于設(shè)計層面來說,概念上還是應(yīng)該存在VO和DTO,因為兩者有著本質(zhì)的區(qū)別,DTO代表服務(wù)層需要接收的數(shù)據(jù)和返回的數(shù)據(jù),而VO代表展示層需要顯示的數(shù)據(jù)。
用一個例子來說明可能會比較容易理解:例如服務(wù)層有一個getUser的方法返回一個系統(tǒng)用戶,其中有一個屬性是gender(性別),對于服務(wù)層來說,它只從語義上定義:1-男性,2-女性,0-未指定,而對于展示層來說,它可能需要用“帥哥”代表男性,用“美女”代表女性,用“秘密”代表未指定。說到這里,可能你還會反駁,在服務(wù)層直接就返回“帥哥美女”不就行了嗎?對于大部分應(yīng)用來說,這不是問題,但設(shè)想一下,如果需求允許客戶可以定制風(fēng)格,而不同風(fēng)格對于“性別”的表現(xiàn)方式不一樣,又或者這個服務(wù)同時供多個客戶端使用(不同門戶),而不同的客戶端對于表現(xiàn)層的要求有所不同,那么,問題就來了。再者,回到設(shè)計層面上分析,從職責(zé)單一原則來看,服務(wù)層只負(fù)責(zé)業(yè)務(wù),與具體的表現(xiàn)形式無關(guān),因此,它返回的DTO,不應(yīng)該出現(xiàn)與表現(xiàn)形式的耦合。
理論歸理論,這到底還是分析設(shè)計層面的思維,是否在實現(xiàn)層面必須這樣做呢?一刀切的做法往往會得不償失,下面我馬上會分析應(yīng)用中如何做出正確的選擇。
VO與DTO的應(yīng)用
上面只是用了一個簡單的例子來說明VO與DTO在概念上的區(qū)別,本節(jié)將會告訴你如何在應(yīng)用中做出正確的選擇。
在以下才場景中,我們可以考慮把VO與DTO二合為一(注意:是實現(xiàn)層面):
當(dāng)需求非常清晰穩(wěn)定,而且客戶端很明確只有一個的時候,沒有必要把VO和DTO區(qū)分開來,這時候VO可以退隱,用一個DTO即可,為什么是VO退隱而不是DTO?回到設(shè)計層面,服務(wù)層的職責(zé)依然不應(yīng)該與展示層耦合,所以,對于前面的例子,你很容易理解,DTO對于“性別”來說,依然不能用“帥哥美女”,這個轉(zhuǎn)換應(yīng)該依賴于頁面的腳本(如JavaScript)或其他機制(JSTL、EL、CSS)。
即使客戶端可以進行定制,或者存在多個不同的客戶端,如果客戶端能夠用某種技術(shù)(腳本或其他機制)實現(xiàn)轉(zhuǎn)換,同樣可以讓VO退隱。
以下場景需要優(yōu)先考慮VO、DTO并存:
上述場景的反面場景
因為某種技術(shù)原因,比如某個框架(如Flex)提供自動把POJO轉(zhuǎn)換為UI中某些Field時,可以考慮在實現(xiàn)層面定義出VO,這個權(quán)衡完全取決于使用框架的自動轉(zhuǎn)換能力帶來的開發(fā)和維護效率提升與設(shè)計多一個VO所多做的事情帶來的開發(fā)和維護效率的下降之間的比對。
如果頁面出現(xiàn)一個“大視圖”,而組成這個大視圖的所有數(shù)據(jù)需要調(diào)用多個服務(wù),返回多個DTO來組裝(當(dāng)然,這同樣可以通過服務(wù)層提供一次性返回一個大視圖的DTO來取代,但在服務(wù)層提供一個這樣的方法是否合適,需要在設(shè)計層面進行權(quán)衡)。
DTO與DO的區(qū)別
首先是概念上的區(qū)別,DTO是展示層和服務(wù)層之間的數(shù)據(jù)傳輸對象(可以認(rèn)為是兩者之間的協(xié)議),而DO是對現(xiàn)實世界各種業(yè)務(wù)角色的抽象,這就引出了兩者在數(shù)據(jù)上的區(qū)別,例如UserInfo和User(對于DTO和DO的命名規(guī)則,請參見筆者前面的一篇博文),對于一個getUser方法來說,本質(zhì)上它永遠不應(yīng)該返回用戶的密碼,因此UserInfo至少比User少一個password的數(shù)據(jù)。而在領(lǐng)域驅(qū)動設(shè)計中,正如第一篇系列文章所說,DO不是簡單的POJO,它具有領(lǐng)域業(yè)務(wù)邏輯。
DTO與DO的應(yīng)用
從上一節(jié)的例子中,細(xì)心的讀者可能會發(fā)現(xiàn)問題:既然getUser方法返回的UserInfo不應(yīng)該包含password,那么就不應(yīng)該存在password這個屬性定義,但如果同時有一個createUser的方法,傳入的UserInfo需要包含用戶的password,怎么辦?在設(shè)計層面,展示層向服務(wù)層傳遞的DTO與服務(wù)層返回給展示層的DTO在概念上是不同的,但在實現(xiàn)層面,我們通常很少會這樣做(定義兩個UserInfo,甚至更多),因為這樣做并不見得很明智,我們完全可以設(shè)計一個完全兼容的DTO,在服務(wù)層接收數(shù)據(jù)的時候,不該由展示層設(shè)置的屬性(如訂單的總價應(yīng)該由其單價、數(shù)量、折扣等決定),無論展示層是否設(shè)置,服務(wù)層都一概忽略,而在服務(wù)層返回數(shù)據(jù)時,不該返回的數(shù)據(jù)(如用戶密碼),就不設(shè)置對應(yīng)的屬性。
對于DO來說,還有一點需要說明:為什么不在服務(wù)層中直接返回DO呢?這樣可以省去DTO的編碼和轉(zhuǎn)換工作,原因如下:
兩者在本質(zhì)上的區(qū)別可能導(dǎo)致彼此并不一一對應(yīng),一個DTO可能對應(yīng)多個DO,反之亦然,甚至兩者存在多對多的關(guān)系。
DO具有一些不應(yīng)該讓展示層知道的數(shù)據(jù)
DO具有業(yè)務(wù)方法,如果直接把DO傳遞給展示層,展示層的代碼就可以繞過服務(wù)層直接調(diào)用它不應(yīng)該訪問的操作,對于基于AOP攔截服務(wù)層來進行訪問控制的機制來說,這問題尤為突出,而在展示層調(diào)用DO的業(yè)務(wù)方法也會因為事務(wù)的問題,讓事務(wù)難以控制。
對于某些ORM框架(如Hibernate)來說,通常會使用“延遲加載”技術(shù),如果直接把DO暴露給展示層,對于大部分情況,展示層不在事務(wù)范圍之內(nèi)(Open session in view在大部分情況下不是一種值得推崇的設(shè)計),如果其嘗試在Session關(guān)閉的情況下獲取一個未加載的關(guān)聯(lián)對象,會出現(xiàn)運行時異常(對于Hibernate來說,就是LazyInitiliaztionException)。
從設(shè)計層面來說,展示層依賴于服務(wù)層,服務(wù)層依賴于領(lǐng)域?qū)?#xff0c;如果把DO暴露出去,就會導(dǎo)致展示層直接依賴于領(lǐng)域?qū)?#xff0c;這雖然依然是單向依賴,但這種跨層依賴會導(dǎo)致不必要的耦合。
對于DTO來說,也有一點必須進行說明,就是DTO應(yīng)該是一個“扁平的二維對象”,舉個例子來說明:如果User會關(guān)聯(lián)若干個其他實體(例如Address、Account、Region等),那么getUser()返回的UserInfo,是否就需要把其關(guān)聯(lián)的對象的DTO都一并返回呢?如果這樣的話,必然導(dǎo)致數(shù)據(jù)傳輸量的大增,對于分布式應(yīng)用來說,由于涉及數(shù)據(jù)在網(wǎng)絡(luò)上的傳輸、序列化和反序列化,這種設(shè)計更不可接受。如果getUser除了要返回User的基本信息外,還需要返回一個AccountId、AccountName、RegionId、RegionName,那么,請把這些屬性定義到UserInfo中,把一個“立體”的對象樹“壓扁”成一個“扁平的二維對象”。筆者目前參與的項目是一個分布式系統(tǒng),該系統(tǒng)不管三七二十一,把一個對象的所有關(guān)聯(lián)對象都轉(zhuǎn)換為相同結(jié)構(gòu)的DTO對象樹并返回,導(dǎo)致性能非常的慢。
DO與PO的區(qū)別
DO和PO在絕大部分情況下是一一對應(yīng)的,PO是只含有g(shù)et/set方法的POJO,但某些場景還是能反映出兩者在概念上存在本質(zhì)的區(qū)別:
DO在某些場景下不需要進行顯式的持久化,例如利用策略模式設(shè)計的商品折扣策略,會衍生出折扣策略的接口和不同折扣策略實現(xiàn)類,這些折扣策略實現(xiàn)類可以算是DO,但它們只駐留在靜態(tài)內(nèi)存,不需要持久化到持久層,因此,這類DO是不存在對應(yīng)的PO的。
同樣的道理,某些場景下,PO也沒有對應(yīng)的DO,例如老師Teacher和學(xué)生Student存在多對多的關(guān)系,在關(guān)系數(shù)據(jù)庫中,這種關(guān)系需要表現(xiàn)為一個中間表,也就對應(yīng)有一個TeacherAndStudentPO的PO,但這個PO在業(yè)務(wù)領(lǐng)域沒有任何現(xiàn)實的意義,它完全不能與任何DO對應(yīng)上。這里要特別聲明,并不是所有多對多關(guān)系都沒有業(yè)務(wù)含義,這跟具體業(yè)務(wù)場景有關(guān),例如:兩個PO之間的關(guān)系會影響具體業(yè)務(wù),并且這種關(guān)系存在多種類型,那么這種多對多關(guān)系也應(yīng)該表現(xiàn)為一個DO,又如:“角色”與“資源”之間存在多對多關(guān)系,而這種關(guān)系很明顯會表現(xiàn)為一個DO——“權(quán)限”。
某些情況下,為了某種持久化策略或者性能的考慮,一個PO可能對應(yīng)多個DO,反之亦然。例如客戶Customer有其聯(lián)系信息Contacts,這里是兩個一對一關(guān)系的DO,但可能出于性能的考慮(極端情況,權(quán)作舉例),為了減少數(shù)據(jù)庫的連接查詢操作,把Customer和Contacts兩個DO數(shù)據(jù)合并到一張數(shù)據(jù)表中。反過來,如果一本圖書Book,有一個屬性是封面cover,但該屬性是一副圖片的二進制數(shù)據(jù),而某些查詢操作不希望把cover一并加載,從而減輕磁盤IO開銷,同時假設(shè)ORM框架不支持屬性級別的延遲加載,那么就需要考慮把cover獨立到一張數(shù)據(jù)表中去,這樣就形成一個DO對應(yīng)多個PO的情況。
PO的某些屬性值對于DO沒有任何意義,這些屬性值可能是為了解決某些持久化策略而存在的數(shù)據(jù),例如為了實現(xiàn)“樂觀鎖”,PO存在一個version的屬性,這個version對于DO來說是沒有任何業(yè)務(wù)意義的,它不應(yīng)該在DO中存在。同理,DO中也可能存在不需要持久化的屬性。
DO與PO的應(yīng)用
由于ORM框架的功能非常強大而大行其道,而且JavaEE也推出了JPA規(guī)范,現(xiàn)在的業(yè)務(wù)應(yīng)用開發(fā),基本上不需要區(qū)分DO與PO,PO完全可以通過JPA,Hibernate Annotations/hbm隱藏在DO之中。雖然如此,但有些問題我們還必須注意:
對于DO中不需要持久化的屬性,需要通過ORM顯式的聲明,如:在JPA中,可以利用@Transient聲明。
對于PO中為了某種持久化策略而存在的屬性,例如version,由于DO、PO合并了,必須在DO中聲明,但由于這個屬性對DO是沒有任何業(yè)務(wù)意義的,需要讓該屬性對外隱藏起來,最常見的做法是把該屬性的get/set方法私有化,甚至不提供get/set方法。但對于Hibernate來說,這需要特別注意,由于Hibernate從數(shù)據(jù)庫讀取數(shù)據(jù)轉(zhuǎn)換為DO時,是利用反射機制先調(diào)用DO的空參數(shù)構(gòu)造函數(shù)構(gòu)造DO實例,然后再利用JavaBean的規(guī)范反射出set方法來為每個屬性設(shè)值,如果不顯式聲明set方法,或把set方法設(shè)置為private,都會導(dǎo)致Hibernate無法初始化DO,從而出現(xiàn)運行時異常,可行的做法是把屬性的set方法設(shè)置為protected。
對于一個DO對應(yīng)多個PO,或者一個PO對應(yīng)多個DO的場景,以及屬性級別的延遲加載,Hibernate都提供了很好的支持,請參考Hibnate的相關(guān)資料。
領(lǐng)域模型命名規(guī)約:
-
數(shù)據(jù)對象:xxxDO,xxx 即為數(shù)據(jù)表名。
-
數(shù)據(jù)傳輸對象:xxxDTO,xxx 為業(yè)務(wù)領(lǐng)域相關(guān)的名稱。
-
展示對象:xxxVO,xxx 一般為網(wǎng)頁名稱。
-
POJO 是 DO/DTO/BO/VO 的統(tǒng)稱,禁止命名成 xxxPOJO。
總結(jié)
以上是生活随笔為你收集整理的DO、DTO、BO、VO、POJO等各种O浅学(总结)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 软考相关/初级程序员上午场
- 下一篇: 电驴提示“该内容尚未提供权利证明,无法提