什麼是 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