概念:
VO(View Object):
視圖對象,用于展示層,它的作用是把某個指定頁面(或組件)的所有數(shù)據(jù)封裝起來。
DTO(Data Transfer Object):
數(shù)據(jù)傳輸對象,這個概念來源于J2EE的設(shè)計模式,原來的目的是為了EJB的分布式應(yīng)用提供粗粒度的數(shù)據(jù)實體,以減少分布式調(diào)用的次數(shù),從而提高分布式調(diào)用的性能和降低網(wǎng)絡(luò)負(fù)載,但在這里,我泛指用于展示層與服務(wù)層之間的數(shù)據(jù)傳輸對象。
DO(Domain Object):
領(lǐng)域?qū)ο?,就是從現(xiàn)實世界中抽象出來的有形或無形的業(yè)務(wù)實體。
PO(Persistent Object):
持久化對象,它跟持久層(通常是關(guān)系型數(shù)據(jù)庫)的數(shù)據(jù)結(jié)構(gòu)形成一一對應(yīng)的映射關(guān)系,如果持久層是關(guān)系型數(shù)據(jù)庫,那么,數(shù)據(jù)表中的每個字段(或若干個)就對應(yīng)PO的一個(或若干個)屬性。
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)用中如何做出正確的選擇。
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ì)上它永遠(yuǎn)不應(yīng)該返回用戶的密碼,因此UserInfo至少比User少一個password的數(shù)據(jù)。而在領(lǐng)域驅(qū)動設(shè)計中,正如第一篇系列文章所說,DO不是簡單的POJO,它具有領(lǐng)域業(yè)務(wù)邏輯。
DO與PO的區(qū)別
DO和PO在絕大部分情況下是一一對應(yīng)的,PO是只含有g(shù)et/set方法的POJO,但某些場景還是能反映出兩者在概念上存在本質(zhì)的區(qū)別:
DO在某些場景下不需要進(jìn)行顯式的持久化,例如利用策略模式設(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,但該屬性是一副圖片的二進(jìn)制數(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中也可能存在不需要持久化的屬性。