💡Value Objects در .NET (مبانی DDD)
Value Object
ها یکی از اجزای اصلی Domain-Driven Design (DDD) هستند.DDD یک رویکرد در توسعهٔ نرمافزار است که برای حل مسائل در دامنههای پیچیده (complex domains) استفاده میشود.
Value Object
ها مجموعهای از مقادیر اولیه (primitive values) و قوانین یا محدودیتهای مرتبط (invariants) را در خود نگه میدارند.
چند نمونهٔ رایج از Value Objectها:
💰 Money (که شامل amount و currency است)
📅 DateRange (که شامل start date و end date است)
🧠 Value Objectها چه هستند؟
بیایید با تعریف اصلی از کتاب Domain-Driven Design شروع کنیم:
شیئی که نمایانگر جنبهای توصیفی از دامنه باشد و هیچ هویت مفهومی (conceptual identity) نداشته باشد، Value Object نامیده میشود.
این اشیاء برای نمایش عناصری از طراحی استفاده میشوند که برای ما فقط به خاطر ماهیتشان مهماند، نه به خاطر اینکه چه کسی یا کدام نمونه هستند.
— Eric Evans
Value Object
ها با Entityها تفاوت دارند؛
زیرا هیچ مفهوم هویتی (identity) ندارند.
در واقع، آنها نوعهای اولیه (primitive types) را در دامنه کپسوله میکنند و از primitive obsession جلوگیری میکنند.
✨ ویژگیهای اصلی Value Objectها
دو ویژگی کلیدی در همهٔ Value Objectها وجود دارد:
Immutable بودن :
پس از ایجاد، مقدار آنها دیگر تغییر نمیکند.
نداشتن Identity : هویت مستقل یا کلید یکتا ندارند.
یک ویژگی مهم دیگر نیز وجود دارد:
⚖️ برابری ساختاری (Structural Equality)
دو Value Object زمانی برابر محسوب میشوند که مقادیر درونی آنها یکسان باشد.
البته در عمل، این ویژگی کماهمیتتر از دو مورد قبلی است،
اما در بعضی موقعیتها لازم است که تنها برخی از مقادیر داخلی در مقایسهٔ برابری در نظر گرفته شوند.
🧱 پیادهسازی Value Objectها
مهمترین ویژگی Value Objectها تغییرناپذیری (immutability) است. مقادیر یک Value Object پس از ساخته شدن، دیگر نمیتوانند تغییر کنند.
اگر بخواهید یکی از مقادیر را تغییر دهید، باید کل Value Object را جایگزین کنید.
در مثال زیر، یک موجودیت به نام Booking داریم که شامل مقادیر ابتدایی (primitive values) برای آدرس و تاریخ شروع و پایان رزرو است:
public class Booking
{
public string Street { get; init; }
public string City { get; init; }
public string State { get; init; }
public string Country { get; init; }
public string ZipCode { get; init; }
public DateOnly StartDate { get; init; }
public DateOnly EndDate { get; init; }
}
میتوانیم این مقادیر اولیه را با دو Value Object به نامهای Address و DateRange جایگزین کنیم:
public class Booking
{
public Address Address { get; init; }
public DateRange Period { get; init; }
}
اما چطور باید Value Objectها را پیادهسازی کرد؟
⚙️ استفاده از C# Records
در #C میتوان از recordها برای نمایش Value Objectها استفاده کرد. Recordها بهصورت پیشفرض immutable هستند و از برابری ساختاری (structural equality) پشتیبانی میکنند — دو ویژگیای که دقیقاً برای Value Objectها نیاز داریم.
برای مثال، میتوانیم یک Value Object به نام Address را با استفاده از record و primary constructor تعریف کنیم:
public record Address(
string Street,
string City,
string State,
string Country,
string ZipCode);
مزیت این روش در اختصار و سادگی آن است.
اما زمانی که بخواهید قوانین (invariants) را هنگام ساخت Value Object اعمال کنید، باید سازندهٔ خصوصی (private constructor) تعریف کنید — که در این حالت مزیت اختصار از بین میرود.
مشکل دیگر در استفاده از recordها، امکان دور زدن این قوانین از طریق عبارت with است.