值物件

计算机科学裡的值物件(value object)是表示簡單實體的小型物件,這類實體的相等只根據其值,不依照其,只要兩個值物件的值相等,即視為相等,這兩個物件不需是同一個物件。

值物件的例子像是錢的總金額,以及日期範圍等。

值物件很小,因此可以將值物件複制數份,而這些值物件表示的是同一個實體,對於值物件,創建一個新物件會比只有一個實例,其他部份再參考此一實例要簡單:因為值物件的定義中隱含一件事,若兩個值物件相等,他們就需一直維持相等。讓值物件是不可變的也有其優點,因為客戶端的程式無法將值物件改為無效的狀態,或是在初始化後進行不正確的操作。

值物件常見於領域驅動設計(DDD)的建構模塊中。

實現
由於各種物件導向的程式语言之間有細微的差異,田此各語言在實現值物件和使用值物件上,都有自的方式以及设计模式。
C#
在C#裡,類別屬於引用類型,而結構體(概念源自C語言的结构体)是值類型。
因此衍生自類別定義的實例是物件,而衍生自結構體定義的則是值物件(更準確的說法,結構體可以宣告屬性readonly,使結構體不可變,因而成為值物件
)。

以下的作法可以在C#類別中加入值物件的性質:

重載方法,確保物件比較時是比較业务逻辑,不是其身份。

运算符重载和的預設行為,使用方法。

重載方法,確保兩個相等的物件有相同的hash。

透過移除屬性setter,以及只從創建子傳遞成員的值的方式,讓類別不可變。

範例:
public record StreetAddress(string Street, string City);

或是以下更多行的例子

public class StreetAddress
{
public StreetAddress(string street, string city)
{
Street = street;
City = city;
}

public string Street { get; }
public string City { get; }
}

C++
C++裡的值物件可以用設定運算子的重載,再使用適當的常數限定符在欄位上(這樣就只會在創建時設定一次),以及類型的方法上。

不過,若欄位本身宣告為const,會有副作用,無法用另一個物件完全覆寫()。若是用const以外的欄位,但是只開放getter存取函式,以此方式實現值物件,就可以做到用另一個物件完全覆寫的功能。

Python
Python有資料類別可以提供相關測試,並且可以用frozen參數設為不可變。

from dataclasses import dataclass

@dataclass(frozen=True)
class StreetAddress:
"""Represents a street address."""

street: str
city: str

Java
值物件在Java 14起開始支援,是data record。

Java和C#和C++不同,其沒有在語言層面支援自定義型態,每一個自定義型態都是參考性能,因此也會有身份和參考語意,不過仍有人在考慮如何支援自定義的值物件。

因此Java程式設計者會創建不可變的物件來模擬值物件,若物件的狀態不會變化,傳參考在語意上和複製值物件一樣。

可以將類別的所有屬性宣告為blank final,,宣告其中所有的屬性都是不可變的型態(例如、,或是依照其規則宣告的其他型態),不能使用像是甚至是的可變形態。也需要定義equals和hashCode,以其值進行比較,而不是使用參考。

已有人用VALJO(VALue Java Object)一詞來指正確定義地不可變值物件,需要符合的較嚴格規則。

public class StreetAddress {
public final String street;
public final String city;

public StreetAddress(String street, String city) {
this.street = street;
this.city = city;
}
public boolean equals(StreetAddress that) {
return getClass()==that.getClass() && street==that.street && city==that.city;
}
public int hashCode() {
return Objects.hash(street, city);
}
}

上述的程式,到Java 14就變簡單了。

public record StreetAddress (String street, String city) {}

Kotlin
data class StreetAddress(val street: String, val city: String)

Kotlin裡,任何型態都可以有構造器shortcut在其型態本身之前(若該型態有本身的話),一方面是宣告欄位,也是設定這些欄位的值。若加上data關鍵字,就會用值物件的邏輯來實現equals和hashCode等。

Ruby
Ruby可以用Data.define類別來支援值物件(自Ruby 3.2導入),自動提供equality、hashing和 immutability.。

StreetAddress = Data.define(:street, :city)

相關條目

  • 数据传输对象
  • 領域驅動設計

*

參考資料

评论 (0)

  • 还没有评论,来抢沙发吧。