计算机科学裡的值物件(value object)是表示簡單實體的小型物件,這類實體的相等只根據其值,不依照其身份,只要兩個值物件的值相等,即視為相等,這兩個物件不需是同一個物件[1][2]。
值物件的例子像是錢的總金額,以及日期範圍等。
值物件很小,因此可以將值物件複制數份,而這些值物件表示的是同一個實體,對於值物件,創建一個新物件會比只有一個實例,其他部份再參考此一實例要簡單[2]。
值物件需是不可變物件[3]:因為值物件的定義中隱含一件事,若兩個值物件相等,他們就需一直維持相等。讓值物件是不可變的也有其優點,因為客戶端的程式無法將值物件改為無效的狀態,或是在初始化後進行不正確的操作[4]。
值物件常見於領域驅動設計(DDD)的建構模塊中。
實現
编辑由於各種物件導向的程式语言之間有細微的差異,田此各語言在實現值物件和使用值物件上,都有自的方式以及设计模式。
C#
编辑在C#裡,類別屬於引用類型,而結構體(概念源自C語言的结构体)是值類型[5]。 因此衍生自類別定義的實例是物件,而衍生自結構體定義的則是值物件(更準確的說法,結構體可以宣告屬性readonly,使結構體不可變,因而成為值物件 [6])。
以下的作法可以在C#類別中加入值物件的性質:
- 重載
Object.Equals方法,確保物件比較時是比較业务逻辑,不是其身份。 - 运算符重载
==和!=的預設行為,使用Equals方法。 - 重載
Object.GetHashCode方法,確保兩個相等的物件有相同的hash。 - 透過移除屬性setter,以及只從創建子傳遞成員的值的方式,讓類別不可變[7][8]。
範例:
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,會有副作用,無法用另一個物件完全覆寫(object1 = object2)。若是用const以外的欄位,但是只開放getter存取函式,以此方式實現值物件,就可以做到用另一個物件完全覆寫的功能。
Python
编辑Python有資料類別可以提供相關測試,並且可以用frozen參數設為不可變[9]。
from dataclasses import dataclass
@dataclass(frozen=True)
class StreetAddress:
"""Represents a street address."""
street: str
city: str
Java
编辑值物件在Java 14起開始支援,是data record[10]。
Java和C#和C++不同,其沒有在語言層面支援自定義型態,每一個自定義型態都是參考性能,因此也會有身份和參考語意[11],不過仍有人在考慮如何支援自定義的值物件[12]。
因此Java程式設計者會創建不可變的物件來模擬值物件[13],若物件的狀態不會變化,傳參考在語意上和複製值物件一樣。
可以將類別的所有屬性宣告為blank final,[14],宣告其中所有的屬性都是不可變的型態(例如、,或是依照其規則宣告的其他型態),不能使用像是ArrayList甚至是Date的可變形態。也需要定義equals和hashCode,以其值進行比較,而不是使用參考。
已有人用VALJO(VALue Java Object)一詞來指正確定義地不可變值物件,需要符合的較嚴格規則[15]。
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.[16]。
StreetAddress = Data.define(:street, :city)
相關條目
编辑參考資料
编辑- ^ Fowler, Martin. Value Object. Catalog of Patterns of Enterprise Application Architecture. Martin Fowler (martinfowler.com). 2003 [17 July 2011]. (原始内容存档于2025-12-04).
- ^ 2.0 2.1 Value Object. Portland Pattern Repository's Wiki. Cunningham & Cunningham, Inc. (c2.com). [6 September 2012]. (原始内容存档于2011-07-22).
- ^ Value Object Should be Immutable. Portland Pattern Repository's Wiki. Cunningham & Cunningham, Inc. (c2.com). [6 September 2012]. (原始内容存档于2012-06-09).
- ^ Burns, Sam. The Value of a Value Object. sam-burns.co.uk. 原始内容存档于April 19, 2015.
- ^ Classes and Structs (C# Programming Guide). Microsoft Developer Network (msdn.microsoft.com). 2012 [5 September 2012]. (原始内容存档于2017-02-03).
- ^ Creating an immutable value object in C# - Part III - Using a struct. Luca Bolognese's WebLog. 2012 [7 September 2012]. (原始内容存档于2012-09-25).
- ^ Koirala, Shivprasad. Immutable objects in C# - CodeProject. www.codeproject.com. [2017-12-26]. (原始内容存档于2025-07-16).
- ^ koirala, Shivprasad. Value Object Design Pattern in C#. www.codeproject.com. [2017-12-26]. (原始内容存档于2025-07-10).
- ^ dataclasses — Data Classes. Python documentation. [7 June 2023]. (原始内容存档于2026-01-30).
- ^ Records Come to Java. [13 April 2021]. (原始内容存档于2021-08-02).
- ^ Java Language Specification, chapter 4. Types, Values, and Variables. [7 October 2015].
- ^ JEP 169: Value Objects. [7 October 2015]. (原始内容存档于2022-05-21).
- ^ Immutable objects. Collected Java Practices. 2012 [5 September 2012]. (原始内容存档于2025-12-31).
- ^ 只有在构造器裡才能設定
- ^ VALJOs - Value Java Objects. [19 October 2014]. (原始内容存档于2014-05-09).
- ^ Class: Data. Ruby documentation. [29 September 2025]. (原始内容存档于2025-12-31).