خانه / OOP / DDD / ValueObject in DDD

ValueObject in DDD

بر طبق تعریف آقای Evans:

“An object that represents a descriptive aspect of the domain with no conceptual identity is called a Value Object. Value Objects are instantiated to represent elements of the design that we care about only for what they are, not who or which they are.” [Eric Evans]

 

مهمترین نکته و وجه تمایز ValueObject و Entity ناظر به وجه Identification هستش. ValueObject ها بر خلاف Entityها دارای Id نیستند و این مهمترین و اصلی ترین جنبه ی تفکیک این دو می باشد. نداشتن Id بدین معنی است که در مورد ValueObjectها نگرانی از بابت این که Object فیزیکی دیگری برابر با ValueObject ما قبلا وجود داشته و ایجاد شده نیستیم. ValueObjectها به نوعی کاملا Interchangeable هستند.

به عنوان نمونه می توان به Money اشاره کرد. اینکه در لحظه ی t1 فرد a میزان 100 تومان پول دارد و فرد b هم در زمان t1 میزان 100 تومان پول دارد. و این دو هیچ گونه ارتباطی با هم ندارند. نکته ی مهم اما در اینجا این هست که ValueObject بودن یا نبودن؛ کاملا توسط DomainContext دیکته خواهد شد. و مثلا در مورد مثال قبلی در صورتی که مثلا History مبلغ با میزان سکه ها در Context برنامه مهم باشند؛ پس Money در اینجا نمی تواند ValueObject باشد و باید به عنوان Entity در نظر گرفته شود.

 

اما ویژگی های ValueObjectها :

  • No Identity: اولین و مهمترین ویژگیValueObjectها نداشتن Id هست. ValueObject بر اساس structural equality با یکدیگیر مقایسه خواهند شد.
  • Immutability: از اونجایی که هرValueObject میتونه کاملا با ValueObject دیگه با Propertyهای مشابه جایگزین شه؛ اصطلاحا اینها دارای خاصیت Immutability هستند. در نتیجه بهتر این است که بجای تغییر دادن یک ValueObject آنرا کاملا با ValueObject جدید جایگزین نمود
  • Lifetime shortening: یکی دیگه از ویژگی های اصلیValueObjectها است. ValueObject بدون Entity نباید وجود داشته باشد. و همیشه یک Composition Relationship بین ValueObject و Entity وجود داره. بدون Entity در کانتکست دامین ValueObject معنایی پیدا نمی کند. Lifetime shortening منجر به ویژگی دیگری خواهد شد که ValueObject بهنگام Map شدن به دیتابیس نباید به جدولی جداگونه Map شوند و باید بصورت inline درون جدول خود Entity قرار بگیرند. (به این مورد بر می گردیم)

تمایش و گرایش بیشتر Developerها علی الخصوص آنهایی که با ORM ها و دیتابیس های RDBMS آشنا هستند؛ به ذخیره ValueObject در جدول جداگانه است. اگر بیش از یک Entity شامل یک ValueObject خاص باشند این امر منجر به مشکلاتی خواهد شد. فرض کنید User و Company هر دو شامل Address هستند. تمایل اولیه جدا کردن Address به درون جدولی جداگانه هست. اما باید توجه داشت که این یک رویکرد اشتباه و bad practice هست. نه تنها منجر به Extend شدن consistency  بین چندین entity خواهد شد, هم چنین فرضیات ذهنی در مورد ValueObject رو به اشتباه می اندازد. از آنجایی که ValueObject در این طراحی باید دارای id باشد. که به راحتی منجر به این می شود که Address به اشتباه به Entity تعبیر شود.

در اینجا code base برای ValueObject آورده می شود:

public abstract class ValueObject<T>

where T : ValueObject<T>

{

public override bool Equals(object obj)

{

var valueObject = obj as T;

 

if (ReferenceEquals(valueObject, null))

return false;

 

return EqualsCore(valueObject);

}

 

protected abstract bool EqualsCore(T other);

 

public override int GetHashCode()

{

return GetHashCodeCore();

}

 

protected abstract int GetHashCodeCore();

 

public static bool operator ==(ValueObject<T> a, ValueObject<T> b)

{

if (ReferenceEquals(a, null) && ReferenceEquals(b, null))

return true;

 

if (ReferenceEquals(a, null) || ReferenceEquals(b, null))

return false;

 

return a.Equals(b);

}

 

public static bool operator !=(ValueObject<T> a, ValueObject<T> b)

{

return !(a == b);

}

}

در بالا base class برای ValueObject در نظر گرفته شده است؛ توجه شود که اولا در اینجا فیلدی به نام id وجود ندارد؛ و اینکه کلاس IEquatable<> رو implement نمی کنه و در عوض EqualsCore() معرفی شده که بصورت abstract هست و ValueObjectهایی که از این کلاس ارث بری  می کنند باید این متد رو ایمپلیمنت کنند.

 

public class Address : ValueObject<Address>

{

public virtual string Street { get; protected set; }

public virtual int ZipCode { get; protected set; }

public virtual string Comment { get; protected set; }

 

public Address(string street, int zipCode, string comment)

{

Contracts.EnsureNotNull(street);

 

Street = street;

ZipCode = zipCode;

Comment = comment;

}

 

protected override bool EqualsCore(Address other)

{

return Street == other.Street && ZipCode == other.ZipCode;

}

 

protected override int GetHashCodeCore()

{

return (ZipCode.GetHashCode() * 397) ^ Street.GetHashCode();

}

}

در اینجا فیلد Comment در EqualsCore و هم چنین GetHashCodeCore استفاده نشده است. چون توضیحی که توسط کاربر ایجاد شده و در اینجا جهت چک کردن برابری تاثیری نخواهد داشت.

درباره ی masoud@admin

پاسخ دهید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

در تلگرام هم همراه شما هستم

اگر علاقمند به معماری نرم افزار و مبحث محبوب مایکروسرویس هستید؛ در کانال با ما همراه باشید. اطلاعات مفید زیادی در این کانال انتظار شما را می کشند. فقط کافیست دکمه ی پیوستن را بفشارید.

پیوستن بستن