پرش به مطلب اصلی

اشیاء ارزش (Value Objects)

مفهوم کلیدی

اشیاء ارزش، مفاهیم دامنه تغییرناپذیر را با ارزش‌هایشان به جای هویتشان نمایش می‌دهند، قوانین کسب‌وکار را اعمال می‌کنند و یکپارچگی داده را حفظ می‌نمایند.

مقدمه

اشیاء ارزش (Value Objects) یک مفهوم اساسی در طراحی مبتنی بر دامنه (Domain-Driven Design یا DDD) هستند که مفاهیم دامنه تغییرناپذیر را با ارزش‌هایشان به جای هویتشان نمایش می‌دهند. آنها ابزاری ضروری برای ایجاد یک مدل دامنه غنی هستند که قوانین کسب‌وکار را اعمال می‌کند و یکپارچگی داده را حفظ می‌نماید.

درک اشیاء ارزش

تعریف دقیق و جهانی برای اشیاء ارزش وجود ندارد زیرا استفاده از آنها به شدت به زمینه خاص بستگی دارد. با این حال، می‌توانیم برخی ویژگی‌ها و اهداف اصلی را تعریف کنیم:

اشیاء ارزش

  • داده را با ارزش نمایش می‌دهند، نه با هویت
  • حاوی رفتار مرتبط با داده‌هایشان هستند
  • همیشه تغییرناپذیر هستند

در مقابل اشیاء انتقال داده

  • DTOها ظروف داده هستند
  • رفتاری در DTOها وجود ندارد
  • می‌توانند تغییرپذیر یا تغییرناپذیر باشند

جایگاه در لایه‌ها

  • اشیاء ارزش به لایه مدل دامنه تعلق دارند
  • آنها نباید در لایه‌های برنامه یا ارائه (مانند Transformerها) ظاهر شوند
  • یک موجودیت می‌تواند یک یا چند شیء ارزش داشته باشد

هدف و مزایا

چرا از اشیاء ارزش استفاده کنیم؟

اشیاء ارزش کمک می‌کنند تا اطمینان حاصل شود که:

  • رفتار نوع داده در سراسر سیستم یکنواخت باقی می‌ماند
  • اعتبارسنجی‌ها به طور یکنواخت اعمال می‌شوند
  • ساختار یکنواخت باقی می‌ماند
  • یکپارچگی داده از طریق تغییرناپذیری حفظ می‌شود

اشیاء ارزش در عمل

اشیاء ارزش مفاهیم خاصی را در سیستم نمایش می‌دهند (مانند ایمیل، موبایل و غیره). اینها تکه‌های منفرد داده هستند که هویت خود را از طریق ویژگی‌ها و اعتبارسنجی‌هایشان دارند.

در نسخه‌های جدیدتر Laravel، برخی ویژگی‌ها عملکردی مشابه با اشیاء ارزش DDD ارائه می‌دهند، اگرچه دقیقاً یکسان نیستند. با این حال، می‌توان از آنها برای دستیابی به اهداف مشابه در زمینه Laravel استفاده کرد.

ویژگی‌های اصلی

تغییرناپذیری (غیرقابل مذاکره)

تغییرناپذیری الزامی است - وضعیت یک شیء ارزش پس از ایجاد نمی‌تواند تغییر کند.

  • پس از ایجاد، وضعیت آنها نمی‌تواند تغییر کند
  • هر تغییری باید منجر به یک نمونه جدید شود
  • وضعیت معتبر را در طول عمر شیء تضمین می‌کند

برابری ارزش

  • بر اساس مقادیر ویژگی‌هایشان مقایسه می‌شوند
  • دو شیء ارزش با مقادیر یکسان برابر در نظر گرفته می‌شوند
  • مثال: دو شیء ارزش Email با "user@example.com" برابر هستند

پیاده‌سازی برابری ارزش

اشیاء ارزش باید بر اساس مقادیرشان قابل مقایسه باشند. برای مثال:

new Email('user@example.com') == new Email('user@example.com') // مقدار true برمی‌گرداند
class Email implements Stringable
{
private function __construct(private readonly string $value)
{
$this->validate($value);
}

public static function fromString(string $value): self
{
return new self($value);
}

// ... بقیه پیاده‌سازی

با پیاده‌سازی این متدها، اشیاء ارزش می‌توانند به درستی مقایسه شوند و در عملیات برابری در سراسر برنامه استفاده شوند.

خود-اعتبارسنجی

  • اشیاء ارزش وضعیت خود را هنگام ایجاد اعتبارسنجی می‌کنند
  • آنها اطمینان می‌دهند که همیشه در وضعیت معتبر هستند
  • از وضعیت نامعتبر از طریق سازنده و متدهای کارخانه‌ای جلوگیری می‌شود
  • تمام قوانین اعتبارسنجی در خود شیء ارزش کپسوله شده‌اند

ماهیت زمینه‌ای

  • استفاده از اشیاء ارزش به شدت به زمینه وابسته است
  • همان مفهوم ممکن است در یک زمینه یک شیء ارزش و در زمینه دیگر یک موجودیت باشد
  • تصمیم باید بر اساس این باشد که آیا شیء هویتی دارد یا با ویژگی‌هایش تعریف می‌شود

دستورالعمل‌های پیاده‌سازی

حضور اشیاء ارزش به عنوان یک شاخص

  • حضور یا عدم حضور اشیاء ارزش در یک سیستم می‌تواند شاخصی از کیفیت طراحی باشد
  • سیستمی با تعداد کم یا بدون اشیاء ارزش ممکن است نشان دهنده موارد زیر باشد:
    • پیاده‌سازی ضعیف قوانین کسب‌وکار در لایه نرم‌افزار
    • یک مدل دامنه کم‌خون
    • نشت منطق کسب‌وکار به سرویس‌ها یا کنترلرها
  • در مقابل، سیستمی با اشیاء ارزش تعریف شده خوب معمولاً نشان دهنده موارد زیر است:
    • قراردادهای قوی‌تر قوانین کسب‌وکار
    • شیوه‌های برنامه‌نویسی دفاعی بهتر
    • کپسوله‌سازی بهتر منطق دامنه

بهبود تدریجی

  • پیاده‌سازی اشیاء ارزش در جایی که مناسب است باید به عنوان شکلی از بهبود مستمر در نظر گرفته شود
  • پایگاه‌های کد موجود می‌توانند به تدریج برای معرفی اشیاء ارزش بازسازی شوند
  • هر شیء ارزش جدید، مدل دامنه را غنی‌تر و بیانگرتر می‌کند

ساختار پایه

class Email implements Stringable
{
private function __construct(private string $value)
{
$this->validate($value);
}

public static function fromString(string $value): self
{
return new self($value);
}

private function validate(string $value): void
{
// منطق اعتبارسنجی در اینجا
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException("قالب ایمیل نامعتبر است");
}
}

public function __toString(): string
{
return $this->value;
}
}

چه زمانی از اشیاء ارزش استفاده کنیم

هشدار

هر قانون کسب‌وکار در سطح داده نیازمند یک شیء ارزش نیست. اشیاء ارزش باید به طور انتخابی برای مفاهیمی استفاده شوند که در بخش‌های مختلف سیستم تکرار می‌شوند و جایی که شناسایی نوع مهم است.

اشیاء ارزش در این سناریوها بیشترین مزیت را دارند:

  1. قوانین کسب‌وکار تکراری

    • زمانی که همان قوانین اعتبارسنجی و رفتارها در چندین بخش از سیستم مورد نیاز هستند
    • برای مفاهیمی که به طور مکرر در دامنه ظاهر می‌شوند (مانند ایمیل، شماره تلفن، پول)
    • زمانی که مدیریت یکنواخت یک نوع داده خاص در سراسر برنامه حیاتی است
  2. مفاهیم دامنه

    • نمایش مفاهیم دامنه با قوانین خاص (ایمیل، شماره تلفن، پول و غیره)
    • کپسوله‌سازی اعتبارسنجی و رفتاری که هویت مفهوم را تعریف می‌کند
    • مثال‌ها: ایمیل، شماره تلفن، آدرس، پول و غیره
  3. ترکیب ارزش

    • گروه‌بندی مقادیر مرتبط که باید به عنوان یک واحد در نظر گرفته شوند
    • مثال: آدرس شامل خیابان، شهر، کد پستی
    • اطمینان از اینکه این مقادیر همیشه با هم استفاده می‌شوند و روابط خود را حفظ می‌کنند
  4. ایمنی نوع و وضوح

    • جلوگیری از وسواس اولیه با ایجاد انواع خاص
    • امضاهای متد را بیانگرتر و خود-مستندساز می‌کند
    • مثال: public function register(Email $email, Password $password) واضح‌تر از register(string $email, string $password) است
  5. اعمال قوانین کسب‌وکار

    • زمانی که قوانین کسب‌وکار خاص باید همیشه برای یک قطعه خاص از داده اعمال شوند
    • اطمینان از اینکه این قوانین نمی‌توانند توسط بخش‌های دیگر سیستم دور زده شوند
    • مثال: یک OrderQuantity که باید مثبت و کمتر از یک مقدار حداکثر باشد
  6. زبان فراگیر

    • برای انعکاس بهتر زبان دامنه در کد شما
    • کد را بیانگرتر و همسو با اصطلاحات کسب‌وکار می‌کند
    • به پر کردن شکاف بین متخصصان دامنه و توسعه‌دهندگان کمک می‌کند

مزایا

  • بهبود وضوح کد: مفاهیم دامنه را به صراحت بیان می‌کند
  • کاهش تکرار: اعتبارسنجی و رفتار را متمرکز می‌کند
  • افزایش ایمنی نوع: از سوء استفاده از انواع اولیه جلوگیری می‌کند
  • تست آسان‌تر: واحدهای خودکفا با مرزهای مشخص

الگوهای رایج

متدهای کارخانه‌ای

public static function fromString(string $value): self
{
return new self($value);
}

// استفاده
$email = Email::fromString('user@example.com');

مجموعه‌های اشیاء ارزش

class EmailCollection extends Collection
{
public function __construct(Email ...$emails)
{
parent::__construct($emails);
}

public function contains(Email $email): bool
{
return $this->contains(fn (Email $e) => $e->equals($email));
}
}

بهترین شیوه‌ها

  1. متمرکز نگه دارید

    • هر شیء ارزش باید یک مسئولیت واحد داشته باشد
    • از منطق پیچیده دامنه که متعلق به سرویس‌های دامنه است، اجتناب کنید
    • محدوده را باریک و به خوبی تعریف شده نگه دارید
  2. تغییرناپذیری سختگیرانه

    • همیشه برای تغییرات، نمونه‌های جدید را برگردانید
    • تمام ویژگی‌ها را خصوصی و فقط-خواندنی کنید
    • از هر متدی که وضعیت داخلی را پس از ساخت تغییر می‌دهد، جلوگیری کنید
    • این امر ایمنی رشته و رفتار قابل پیش‌بینی را تضمین می‌کند
  3. اعتبارسنجی جامع

    • تمام ورودی‌ها را در سازنده اعتبارسنجی کنید
    • استثناهای معنادار و خاص دامنه برای داده‌های نامعتبر پرتاب کنید
    • استفاده از الگوی متد کارخانه‌ای را برای اعتبارسنجی‌های پیچیده در نظر بگیرید
    • قوانین اعتبارسنجی را در بلوک docblock کلاس مستند کنید
  4. آگاهی از لایه

    • اشیاء ارزش را در لایه مدل دامنه نگه دارید
    • اجازه ندهید به لایه‌های برنامه یا ارائه نشت کنند
    • از DTOها یا الگوهای دیگر برای انتقال داده بین لایه‌ها استفاده کنید
    • این کار جداسازی لایه و مرزهای معماری را حفظ می‌کند
  5. ادغام با Laravel

    • از castهای سفارشی برای تعامل بی‌درنگ با پایگاه داده استفاده کنید
    • در صورت نیاز، Arrayable و Jsonable را پیاده‌سازی کنید
    • استفاده از مجموعه‌های اشیاء ارزش را برای مدیریت چندین مقدار در نظر بگیرید
    • هنگام استفاده از کش Laravel، به سریالی‌سازی/غیرسریالی‌سازی توجه کنید
  6. ملاحظات تست

    • موارد حاشیه‌ای را در قوانین اعتبارسنجی تست کنید
    • تغییرناپذیری را در تست‌ها تأیید کنید
    • معنای برابری ارزش را تست کنید
    • تست مبتنی بر ویژگی را برای اشیاء ارزش پیچیده در نظر بگیرید

مثال: شیء ارزش ایمیل

class Email implements Stringable
{
private function __construct(private string $value)
{
$this->validate();
}

public static function fromString(string $value): self
{
return new self($value);
}

private function validate(): void
{
if (!filter_var($this->value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidEmailException("ایمیل '{$this->value}' یک آدرس ایمیل معتبر نیست.");
}

// قوانین کسب‌وکار اضافی می‌توانند اینجا اضافه شوند
// برای مثال، بررسی دامنه‌های ایمیل یکبار مصرف
}

public function getDomain(): string
{
return explode('@', $this->value, 2)[1] ?? '';
}

public function equals(self $other): bool
{
return strtolower($this->value) === strtolower($other->value);
}

public function __toString(): string
{
return $this->value;
}
}

مثال: شیء ارزش پول

class Money
{
public function __construct(
private readonly float $amount,
private readonly string $currency
) {
$this->validate();
}

private function validate(): void
{
if ($this->amount < 0) {
throw new InvalidArgumentException('مقدار نمی‌تواند منفی باشد');
}

if (!in_array($this->currency, ['USD', 'EUR', 'GBP'], true)) {
throw new InvalidArgumentException('ارز پشتیبانی نشده');
}
}

public function add(Money $other): self
{
if ($this->currency !== $other->currency) {
throw new InvalidArgumentException('ارزها باید مطابقت داشته باشند');
}

return new self($this->amount + $other->amount, $this->currency);
}

// متدهای دیگر...
}

نتیجه‌گیری

اشیاء ارزش ابزاری قدرتمند برای ایجاد یک مدل دامنه غنی و بیانگر هستند. با کپسوله‌سازی اعتبارسنجی و رفتار، آنها به حفظ یکپارچگی داده کمک می‌کنند و کدبیس را قابل نگهداری‌تر و خود-مستندساز می‌کنند. شروع به شناسایی مقادیر اولیه در دامنه خود کنید که می‌توانند از تبدیل شدن به اشیاء ارزش بهره‌مند شوند تا کد شما را قوی‌تر و بیانگرتر کنید.