淺析 VO、DTO、DO、PO 的概念、區別和用處

大家好,我是不才陳某~

本篇文章主要討論一下我們經常會用到的一些對象:VO、DTO、DO 和 PO。

由於不同的項目和開發人員有不同的命名習慣,這裏我首先對上述的概念進行一個簡單描述,名字只是個標識,我們重點關注其概念;

概念

模型

下面以一個時序圖建立簡單模型來描述上述對象在三層架構應用中的位置

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 並存:

DTO 與 DO 的區別

首先是概念上的區別,DTO 是展示層和服務層之間的數據傳輸對象(可以認爲是兩者之間的協議),而 DO 是對現實世界各種業務角色的抽象,這就引出了兩者在數據上的區別,例如 UserInfo 和 User,對於一個 getUser 方法來說,本質上它永遠不應該返回用戶的密碼,因此 UserInfo 至少比 User 少一個 password 的數據。而在領域驅動設計中,正如第一篇系列文章所說,DO 不是簡單的 POJO,它具有領域業務邏輯。

對於 DTO 和 DO 的命名規則,請參見:

https://www.cnblogs.com/qixuejia/p/10789612.html

DTO 與 DO 的應用

從上一節的例子中,細心的讀者可能會發現問題:既然 getUser 方法返回的 UserInfo 不應該包含 password,那麼就不應該存在 password 這個屬性定義,但如果同時有一個 createUser 的方法,傳入的 UserInfo 需要包含用戶的 password,怎麼辦?

在設計層面,展示層向服務層傳遞的 DTO 與服務層返回給展示層的 DTO 在概念上是不同的,但在實現層面,我們通常很少會這樣做(定義兩個 UserInfo,甚至更多),因爲這樣做並不見得很明智,我們完全可以設計一個完全兼容的 DTO,在服務層接收數據的時候,不該由展示層設置的屬性(如訂單的總價應該由其單價、數量、折扣等決定),無論展示層是否設置,服務層都一概忽略,而在服務層返回數據時,不該返回的數據(如用戶密碼),就不設置對應的屬性。

對於 DO 來說,還有一點需要說明:爲什麼不在服務層中直接返回 DO 呢?這樣可以省去 DTO 的編碼和轉換工作,原因如下:

對於 DTO 來說,也有一點必須進行說明,就是 DTO 應該是一個 “扁平的二維對象”,舉個例子來說明:如果 User 會關聯若干個其他實體(例如 Address、Account、Region 等),那麼 getUser() 返回的 UserInfo,是否就需要把其關聯的對象的 DTO 都一併返回呢?

如果這樣的話,必然導致數據傳輸量的大增,對於分佈式應用來說,由於涉及數據在網絡上的傳輸、序列化和反序列化,這種設計更不可接受。如果 getUser 除了要返回 User 的基本信息外,還需要返回一個 AccountId、AccountName、RegionId、RegionName,那麼,請把這些屬性定義到 UserInfo 中,把一個 “立體” 的對象樹 “壓扁” 成一個“扁平的二維對象”,筆者目前參與的項目是一個分佈式系統,該系統不管三七二十一,把一個對象的所有關聯對象都轉換爲相同結構的 DTO 對象樹並返回,導致性能非常的慢。

DO 與 PO 的區別

DO 和 PO 在絕大部分情況下是一一對應的,PO 是隻含有 get/set 方法的 POJO,但某些場景還是能反映出兩者在概念上存在本質的區別:

DO 與 PO 的應用

到目前爲止,相信大家都已經比較清晰的瞭解 VO、DTO、DO、PO 的概念、區別和實際應用了。通過上面的詳細分析,我們還可以總結出一個原則:

分析設計層面和實現層面完全是兩個獨立的層面,即使實現層面通過某種技術手段可以把兩個完全獨立的概念合二爲一,在分析設計層面,我們仍然(至少在頭腦中)需要把概念上獨立的東西清晰的區分開來,這個原則對於做好分析設計非常重要(工具越先進,往往會讓我們越麻

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/AvHTM8WAlXUMhNpb5k-HUQ