Java中各種O(PO,BO,DTO,VO等) 是不是人為增加系統復雜度?
在Java和其他編程語言的開發過程中,經常會用到幾個以"O"結尾的縮寫,比如PO,BO,DTO,VO等等,O在這里是Object的縮寫,不同的O代表了不同的數據類型,很多時候這些O中的屬性看起來都是差不多的,干的事情好像也只是一個簡單的封裝,那么搞出這么多O出來是不是人為增加了系統的復雜度呢?
各種O都是干什么的?
想要搞清楚標題中的問題,我們首先得了解這些O都是什么東西?這里給大家介紹幾種常見的O:
- PO (Persistent Object) - 持久化對象。持久化對象通常對應數據庫中的一個表,主要用于表示數據庫中存儲的數據。PO中的屬性通常和數據表的列一一對應,用于ORM(對象關系映射)框架中,如Hibernate,JPA等。
- BO (Business Object) - 業務對象。業務對象主要封裝了業務邏輯。它可以包含多個PO,或者是一個PO的擴展,增加了業務處理的邏輯。BO通常在業務層被使用,用于實現業務操作,比如計算、決策等。
- VO (Value Object) - 值對象。值對象是一種用于傳輸數據的簡單對象,它通常不包含業務邏輯,只包含數據屬性和get/set方法。值對象主要用于業務層與表示層之間的數據傳遞,它的數據可能是由多個PO組合而成。
- DTO (Data Transfer Object) - 數據傳輸對象。數據傳輸對象類似于VO,它也是用于層與層之間的數據傳遞。DTO通常用于遠程通信,比如Web服務之間的數據傳遞。DTO通常不包含任何業務邏輯,只是用于在不同層次或不同系統之間傳輸數據。
有時候我們還會看到DO、POJO等概念,它們又是什么呢?
- DO (Domain Object) - 領域對象。領域對象是指在問題領域內被定義的對象,它可以包含數據和行為,并且通常代表現實世界中的實體。在DDD(領域驅動設計)中,領域對象是核心概念,用于封裝業務邏輯和規則。這里需要注意DO和BO的區別,雖然都是搞業務邏輯,DO通常是業務領域中單一實體的抽象,它關注于單個業務實體的屬性和行為;而BO則通常涉及到業務流程的實現,可能會協調多個DO來完成一個業務操作。
- POJO (Plain Old Java Object) - 簡單老式Java對象。POJO是指沒有遵循特定Java對象模型、約定或框架(如EJB)的簡單Java對象。POJO通常用于表示數據結構,它們的實例化和使用不依賴于特定的容器或框架。
為什么要劃分各種O?
在軟件開發中劃分不同的O主要是為了實現關注點分離(Separation of Concerns,SoC),提高代碼的可維護性、可讀性和可擴展性。
下面展開列舉了一些劃分這些對象的原因:
- 明確職責:通過將不同的職責分配給不同的對象,可以使每個對象都有明確的職責,這樣代碼更容易理解和維護。
- 減少耦合:不同層次之間通過定義清晰的接口(如特定的對象)交互,減少了直接的依賴關系,降低了耦合度。
- 抽象層次:通過定義不同的對象,可以在不同的抽象層次上操作,比如在數據層處理PO,在業務層處理BO,這樣可以在合適的層次上做出決策。
-
- 靈活性:當系統需要變更時,由于職責和層次的清晰劃分,更容易做出局部的修改而不影響到整個系統。不同的對象可能針對性能有不同的優化,例如PO可能被優化以提高數據庫操作的性能。
- 安全性:通過使用不同的對象,可以控制敏感數據的暴露。例如,可以在DTO中排除一些不應該傳輸到前端的敏感信息。
- 測試性:分離的對象使得單元測試變得更加容易,因為可以針對每個對象進行獨立的測試。
- 交互清晰:在不同的系統組件或層次之間傳遞數據時,清晰的對象定義可以讓數據交互更加清晰,減少數據傳遞中的錯誤。
總之,通過劃分各種“O”對象,開發者可以更好地組織代碼,將復雜系統分解為更小、更易于管理的部分,同時也有助于團隊成員之間的溝通和協作。這種劃分在設計模式和軟件工程實踐中是一種常見且有效的方法。
OO不分的慘痛經歷
說個實際的慘痛經驗。
很多時候我會感覺這些O之間存在很多重復的代碼,比如重復的屬性定義、簡單的方法封裝,DRY(Don't Repeat Yourself)原則不是說讓大家避免重復嘛,所以我也曾經嘗試在程序中統一它們。
但是總有一些O之間存在或多或少的差異,比如:
- 這個O需要一個A屬性,僅用于內部狀態管理,不會暴露到外部,其它O都不需要。
- 還有這個接口需要返回一個B屬性,其它接口都不需要。
這時候,你怎么辦?如果使用同一個類型,那就得加上這些屬性,盡管它們在某些時候用不到。根據你的選擇,你可能在所有的地方都給這個屬性賦值,也可能僅在業務需要的時候給他們賦值。
看個實際的例子:在一個復雜的電商系統中,商品的管理可能涉及到庫存管理、價格策略、促銷信息等多個方面。
// 商品類
public class Product {private Long id; // 來自商品表private String name; // 來自商品表private double price; // 來自商品表,傳輸時需要特殊格式private int stock; // 來自庫存表,僅在下單判斷中需要,展示層不需要private String promotionInfo; // 來自促銷表,展示層需要// 構造器、getter和setter方法省略
}
但是這卻帶來了很大的危害:
- 調用接口的同學會問,這個屬性什么時候會有值,什么時候會沒值?
- 優化的同學會問,計算這個屬性的值會影響性能,能刪掉嗎?
- 交接的同學會問,這個屬性是干什么用的,為什么不給他賦值?
總之會增加了大量的溝通成本與維護難度。一旦這樣做了,后邊就會特別別扭,改不完,根本改不完。
在軟件工程化的今天,各類O的設計看似增加了復雜度,但是實際上是對系統模塊化、職責劃分以及實際應用場景的合理抽象和封裝,有助于提高軟件質量和團隊協作效率。
老老實實寫吧,不同的O就是不同的東西,它們不是重復的,只是在代碼上看著像,就像人有四肢,動物也有四肢,但是它們不能共用,否則出來的就是四不像。
圖片來源:https://ozhanozturk.com/2018/01/28/chimera-kimera-mitoloji/
當然如果只是一個很簡單的程序或者一次性的程序,我們確實沒必要劃分這么多的O出來,直接在接口方法中訪問數據庫也不是不可以的。
前端中O的使用
雖然各種O一般活躍在各種后端程序中,但是前端也不乏O的身影,只是沒有后端那么形式化。
以下是一些可能在前端開發中遇到的以“O”結尾的數據對象:
- VO (View Object) - 視圖對象。在前端框架中,VO可以代表專門為視圖層定制的數據對象。這些對象通常是從后端接口獲取的數據經過加工或格式化后,用于在界面上顯示的對象。
- DTO (Data Transfer Object) - 數據傳輸對象。雖然DTO通常用于后端服務間的數據傳輸,但在前端中也可以用來表示從后端接口獲取的數據結構。前端的DTO通常是指通過Ajax或Fetch API從服務器獲取的原始數據結構。
- VMO (ViewModel Object) - 視圖模型對象。在MVVM(Model-View-ViewModel)架構中,VMO可以代表視圖模型對象,它是模型和視圖之間的連接器。在Vue.js中,Vue實例本身就可以被看作是一個VMO,因為它包含了數據和行為,同時也是視圖的反映。
- SO (State Object) - 狀態對象 盡管不是標準的術語,但在使用如Vuex這樣的狀態管理庫時,SO可以用來指代代表應用狀態的對象。這些狀態對象通常包含了應用的核心數據,如用戶信息、應用設置等。
在實際的Vue開發過程中,開發者可能不會嚴格區分這些概念,而是更多地關注于組件的狀態、屬性(props)、事件和生命周期。組件內部的數據通常以數據屬性(data)的形式存在,而組件間的數據傳遞通常使用屬性(props)和事件(emits)。在處理與后端的數據交互時,開發者可能會定義一些專門的對象來適應后端的接口,但是這些都不是Vue框架強制的概念或規則。
簡單地說,這些“O”其實就是幫我們把代碼寫得更清晰、更有條理,雖然一開始看著很麻煩,但時間一長,你會發現這樣做能省下不少力氣。就像我們的衣柜,雖然分類放好衣服需要點時間,但每天早上起來挑衣服的時候,不就輕松多了嗎?
所以,別被這些專業術語嚇到,它們其實就是幫我們把事情做得更好的小工具。每個“O”都有它的用處和場景,我們要做的,就是搞清楚什么時候戴哪頂帽子,這樣生活和編程都能變得輕松愉快。記住,合適的工具用在合適的地方,能讓你事半功倍!
關注螢火架構,加速技術提升!