什麼是 Data Class#
Data Class 顧名思義就是「只有資料的類別」。為什麼這會是程式碼異味(Code Smell)?
物件導向世界裡,物件靠「行為」彼此互動。沒有行為,物件就不會「動」,事情就沒人做。為了完成工作,這些缺少行為的資料只能仰賴外部的另一個類別來操作它們,於是就產生了耦合與分散。
範例#
以下這個類別保留了行為(getFullName)在自己身上,所以還沒有 Data Class 壞味道。讀者可以試著把 getFullName 搬到別處,看看可讀性會變差多少:
@Data
public class Student {
String lastName;
String firstName;
public Student(String firstName, String lastName) {
this.lastName = lastName;
this.firstName = firstName;
}
// 試著把這個方法搬走?
public String getFullName() {
return firstName + " " + lastName;
}
}如果 getFullName 被搬到外部 Service,那麼當 Student 的欄位名稱有變,所有依賴它的外部呼叫端都要改,且測試會跟著壞。
解決之道#
利用 Move Method 把行為搬回物件本身。Data Class 的判斷重點不在「只有資料」,而是「行為是否被錯誤地放在外面」。
「只有資料的物件」就一定有 Data Class 壞味道嗎#
不一定。Data Class 之所以為壞味道,展現在耦合度上:物件本來可以自己完成的行為卻被外部代勞,欄位一變動,外部就被迫跟著改。
那麼 VO、DTO 是不是壞味道?只是把資料傳給 View 或前端網頁,沒有行為,這樣可以嗎?答案是可以。一個物件如果沒有行為、也沒有「另一處錯誤放置的行為」與其搭配,就不算 Data Class。
用 CQRS 視角來判斷#
搭配 CQRS(Command Query Responsibility Segregation)的觀念來看會更清楚。Ajay Kumar 在 CQRS 一書中建議:
- 命令指令(Command):交給 Entity 處理,因為它有狀態、需要保證不變式
- 查詢指令(Query):可以依查詢者需求任意組裝物件
例如教務處系統:
Student是核心 Entity,會出現在多個 Use Case 中。getFullName這類行為應該放在Student自己身上- 「登入歡迎頁」需要的資料當查詢回傳給前端時,可以用一個沒有行為的物件來承載,因為前端要的就只是資料
不是所有「只有資料的物件」都有 Data Class 壞味道。要看搭配的場景與行為位置而定。
延伸思考#
Data Class 是不是很容易進一步造成 Feature Envy?為什麼?
提示:當資料與行為被分開放置,外部的行為勢必要一直跟資料物件互動,這正是 Feature Envy 的典型樣貌。
原文出處#
- 原書/iThome:https://ithelp.ithome.com.tw/articles/10263695