一、PO/DO/VO/DTO/BO/POJO的介紹
PO(Persistent Object)=DO(Data Object)
持久化對象,它跟持久層(通常是關系型數據庫)的數據結構形成一一對應的映射關系,如果持久層是關系型數據庫,那么,數據表中的每個字段(或若干個)就對應PO的一個(或若干個)屬性。通過 DAO 層向上傳輸數據源對象。
VO(View Object)
視圖對象,用於展示層,它的作用是把某個指定頁面(或組件)的所有數據封裝起來。
簡單理解為頁面展示用的數據。
DTO(Data Transfer Object)
數據傳輸對象,Service或Manager向外傳輸的對象。這個概念來源於J2EE的設計模式,原來的目的是為了EJB的分布式應用提供粗粒度的數據實體,以減少分布式調用的次數,從而提高分布式調用的性能和降低網絡負載,但在這里,我泛指用於展示層與服務層之間的數據傳輸對象。
簡單理解,例如一張數據庫表有50個字段,那么PO就有50個屬性,但是我們在遠程服務或者頁面顯示只需要10個字段。這時就沒有必要傳輸所有的字段,而是用10個屬性的DTO來進行傳遞。
BO( Business Object)
業務對象。 由Service層內封裝的臨時業務邏輯的對象。通過調用 DAO 方法 , 結合 PO、VO 進行業務操作。 一個BO對象可以包括多個PO對象。如常見的工作簡歷例子為例,簡歷可以理解為一個BO,簡歷又包括工作經歷,學習經歷等,這些可以理解為一個個的PO,由多個PO組成BO。復雜例子PO1是交易記錄,PO2是登錄記錄,PO3是商品瀏覽記錄,PO4是添加購物車記錄,PO5是搜索記錄,BO是個人網站行為對象。
BO是一個業務對象,一類業務就會對應一個BO,數量上沒有限制,而且BO會有很多業務操作,也就是說除了get,set方法以外,BO會有很多針對自身數據進行計算的方法。
現在很多持久層框架自身就提供了數據組合的功能,因此BO有可能是在業務層由業務來拼裝PO而成,也有可能是在數據庫訪問層由框架直接生成。很多情況下為了追求查詢的效率,框架跳過PO直接生成BO的情況非常普遍,PO只是用來增刪改使用。(左邊這句話,?,真的會這么做嗎?)
POJO( Plain Ordinary Java Object)
專指只有setter/getter/toString的簡單類,包括DO/DTO/BO/VO等。
POJO是DO/DTO/BO/VO的統稱,禁止命名成xxxPOJO。
大致的示例代碼
-
Controller層:public List<UserVO> getUsers(UserQuery userQuery)。此層常見的轉換為:DTO轉VO
-
Query:數據查詢對象,各層接收上層的查詢請求。注意超過 2 個參數的查詢封裝,禁止使用 Map 類來傳輸。
-
-
Service/Manager層:List<UserDTO> getUsers(UserQuery userQuery)。然后在Service內部使用UserBO封裝中間所需的邏輯對象,此層常見的轉換為:PO轉DTO,或PO轉BO轉DTO
-
DAO層:List<UserPO> getUsers(UserQuery userQuery);
二、區別
2.1 VO和DTO的區別與應用
VO和DTO的區別
大家可能會有個疑問(在筆者參與的項目中,很多程序員也有相同的疑惑):既然DTO是展示層與服務層之間傳遞數據的對象,為什么還需要一個VO呢?對!對於絕大部分的應用場景來說,DTO和VO的屬性值基本是一致的,而且他們通常都是POJO,因此沒必要多此一舉,但不要忘記這是實現層面的思維,對於設計層面來說,概念上還是應該存在VO和DTO,因為兩者有着本質的區別,DTO代表服務層需要接收的數據和返回的數據,而VO代表展示層需要顯示的數據。
用一個例子來說明可能會比較容易理解:例如服務層有一個getUser的方法返回一個系統用戶,其中有一個屬性是gender(性別),對於服務層來說,它只從語義上定義:1-男性,2-女性,0-未指定,而對於展示層來說,它可能需要用“帥哥”代表男性,用“美女”代表女性,用“秘密”代表未指定。說到這里,可能你還會反駁,在服務層直接就返回“帥哥美女”不就行了嗎?對於大部分應用來說,這不是問題,但設想一下,如果需求允許客戶可以定制風格,而不同風格對於“性別”的表現方式不一樣,又或者這個服務同時供多個客戶端使用(不同門戶),而不同的客戶端對於表現層的要求有所不同,那么,問題就來了。再者,回到設計層面上分析,從職責單一原則來看,服務層只負責業務,與具體的表現形式無關,因此,它返回的DTO,不應該出現與表現形式的耦合。
理論歸理論,這到底還是分析設計層面的思維,是否在實現層面必須這樣做呢?一刀切的做法往往會得不償失,下面我馬上會分析應用中如何做出正確的選擇。
VO與DTO的應用
上面只是用了一個簡單的例子來說明VO與DTO在概念上的區別,本節將會告訴你如何在應用中做出正確的選擇。
在以下才場景中,我們可以考慮把VO與DTO二合為一(注意:是實現層面):
-
當需求非常清晰穩定,而且客戶端很明確只有一個的時候,沒有必要把VO和DTO區分開來,這時候VO可以退隱,用一個DTO即可,為什么是VO退隱而不是DTO?回到設計層面,服務層的職責依然不應該與展示層耦合,所以,對於前面的例子,你很容易理解,DTO對於“性別”來說,依然不能用“帥哥美女”,這個轉換應該依賴於頁面的腳本(如JavaScript)或其他機制(JSTL、EL、CSS)
-
即使客戶端可以進行定制,或者存在多個不同的客戶端,如果客戶端能夠用某種技術(腳本或其他機制)實現轉換,同樣可以讓VO退隱
以下場景需要優先考慮VO、DTO並存:
-
上述場景的反面場景
-
因為某種技術原因,比如某個框架(如Flex)提供自動把POJO轉換為UI中某些Field時,可以考慮在實現層面定義出VO,這個權衡完全取決於使用框架的自動轉換能力帶來的開發和維護效率提升與設計多一個VO所多做的事情帶來的開發和維護效率的下降之間的比對。
-
如果頁面出現一個“大視圖”,而組成這個大視圖的所有數據需要調用多個服務,返回多個DTO來組裝(當然,這同樣可以通過服務層提供一次性返回一個大視圖的DTO來取代,但在服務層提供一個這樣的方法是否合適,需要在設計層面進行權衡)。
2.2 BO和DTO的區別
這兩個的區別主要是就是字段的刪減。
BO對內,為了進行業務計算需要輔助數據,或者是一個業務有多個對外的接口,BO可能會含有很多接口對外所不需要的數據,因此DTO需要在BO的基礎上,只要自己需要的數據,然后對外提供。
在這個關系上,通常不會有數據內容的變化,內容變化要么在BO內部業務計算的時候完成,要么在解釋VO的時候完成。
三、相關的代碼規范:
命名風格
【強制】類名使用 UpperCamelCase 風格,必須遵從駝峰形式,但以下情形例外:DO / BO /DTO / VO / AO 正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion 反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion
【參考】各層命名規約:
-
A)Service/DAO層方法命名規約
-
1) 獲取單個對象的方法用get做前綴。
-
2) 獲取多個對象的方法用list做前綴。
-
3) 獲取統計值的方法用count做前綴。
-
4) 插入的方法用save/insert做前綴。
-
5) 刪除的方法用remove/delete做前綴。
-
6) 修改的方法用update做前綴。
-
-
B)領域模型命名規約
-
1) 數據對象:xxxDO,xxx即為數據表名。
-
2) 數據傳輸對象:xxxDTO,xxx為業務領域相關的名稱。
-
3) 展示對象:xxxVO,xxx一般為網頁名稱。
-
4) POJO是DO/DTO/BO/VO的統稱,禁止命名成xxxPOJO。
-
OOP 規約
【強制】定義 DO/DTO/VO 等 POJO 類時,不要設定任何屬性默認值。
反例:POJO類的 addTime 默認值為new Date(); 但是這個屬性在數據提取時並沒有置入具體值,在更新其它字段時又附帶更新了此字段,導致創建時間被修改成當前時間。
