سی ام اس نویسسی ام اس نویسیاد بگیر، سیستم خودتو بنویس
مجلهتماس با ما
سی ام اس نویسسی ام اس نویس

CMS نویس مرجعی برای آموزش برنامه نویسی وب و PHP، توسعه افزونه‌های اختصاصی وردپرس، طراحی ورک‌فلو و اتوماسیون با n8n و توسعه CMS های اختصاصی است. هدف ما ارائه آموزش‌های کاربردی و راهکارهای حرفه‌ای برای توسعه‌دهندگان، کسب‌وکارها و علاقه‌مندان به دنیای برنامه‌نویسی است.

پیوندهای سریع
محصولات فیزیکیمحصولات دیجیتالدوره‌های آموزشی
سی ام اس نویس
مجله سی ام اس نویستماس با ماپنل کاربری
سی ام اس نویس — تمامی حقوق برای سی ام اس نویس محفوظ است. © 1406 تماس با ما09307711941
Specification Pattern در PHP چیست؟ آموزش الگوی Specification با مثال عملی

Specification Pattern در PHP چیست؟ آموزش الگوی Specification با مثال عملی

ممدیر کل سیستم ۲۵ شهریور ۱۴۰۵ ۲۰ دقیقه مطالعه ۱ بازدید
Specification Pattern در PHP چیست؟ آموزش الگوی Specification با مثال عملی

وقتی یک پروژه PHP کوچک است، نوشتن قوانین کسب‌وکار با چند شرط ساده معمولاً مشکلی ایجاد نمی‌کند:


if ($user->isActive() && $user->isPremium()) {

    // ...

}

اما با بزرگ‌تر شدن پروژه، همین شرط‌ها ممکن است در قسمت‌های مختلف تکرار شوند.

برای مثال ممکن است یک قانون در Controller، Service، Repository و Job استفاده شود:


if (

    $user->isActive() &&

    $user->isPremium() &&

    $user->totalPurchases() > 5

) {

    // ...

}

بعد از مدتی قوانین پیچیده‌تر می‌شوند:


if (

    ($user->isActive() && $user->isPremium())

    ||

    ($user->isActive() && $user->totalPurchases() > 10)

) {

    // ...

}

در چنین شرایطی مدیریت Business Logic دشوارتر می‌شود.

یکی از الگوهایی که برای حل این مسئله استفاده می‌شود Specification Pattern است.

در این الگو، هر قانون کسب‌وکار به یک شیء مستقل تبدیل می‌شود و Specificationهای مختلف می‌توانند با منطق‌هایی مانند AND، OR و NOT با یکدیگر ترکیب شوند.


Specification Pattern در PHP چیست؟

Specification Pattern یک الگوی طراحی برای کپسوله‌کردن قوانین کسب‌وکار به صورت اشیای مستقل و قابل ترکیب است.

ایده اصلی بسیار ساده است:

> به جای اینکه قوانین مختلف را در ifهای متعدد پخش کنیم، هر قانون را در یک کلاس مستقل قرار می‌دهیم.

برای مثال فرض کنید می‌خواهیم بررسی کنیم یک کاربر واجد شرایط دریافت تخفیف است یا خیر.

ممکن است قانون ما این باشد:


کاربر باید فعال باشد

و

کاربر باید Premium باشد

به جای نوشتن:


if ($user->active && $user->premium) {

    // ...

}

می‌توانیم دو Specification بسازیم:


ActiveUserSpecification

PremiumUserSpecification

و آن‌ها را با هم ترکیب کنیم.

در نهایت چیزی شبیه این خواهیم داشت:


$specification = $activeUser->and($premiumUser);



if ($specification->isSatisfiedBy($user)) {

    // User is eligible

}

متد اصلی Specification معمولاً isSatisfiedBy() است و مشخص می‌کند آیا یک Candidate قانون موردنظر را رعایت می‌کند یا خیر.


# چه مشکلی باعث استفاده از Specification Pattern می‌شود؟

فرض کنید یک فروشگاه اینترنتی داریم.

برای خرید یک محصول خاص، کاربر باید:

  • فعال باشد
  • حساب تأییدشده داشته باشد
  • حداقل ۵ خرید قبلی داشته باشد

ممکن است ابتدا چنین کدی بنویسیم:


if (

    $user->active &&

    $user->verified &&

    $user->purchaseCount >= 5

) {

    // Allow purchase

}

مشکل زمانی شروع می‌شود که همین قانون در چند قسمت استفاده شود.

مثلاً:


Controller

Service

Command

Job

Notification

Repository

اگر قانون تغییر کند، باید چند قسمت مختلف را اصلاح کنیم.

حتی ممکن است یک قسمت را فراموش کنیم.

در نتیجه سیستم ممکن است در دو قسمت رفتار متفاوتی داشته باشد.

Specification Pattern تلاش می‌کند این قانون را به یک مفهوم مستقل تبدیل کند:


ActiveUser

VerifiedUser

MinimumPurchases

سپس می‌توانیم آن‌ها را ترکیب کنیم.

این یکی از کاربردهای اصلی Specification Pattern است: تبدیل Business Rule به یک شیء قابل استفاده مجدد و قابل ترکیب.


# اجزای اصلی Specification Pattern

برای درک بهتر این الگو، چهار مفهوم مهم را باید بشناسیم.

۱. Specification

Specification همان قانون کسب‌وکار است.

مثلاً:


کاربر فعال است

یا:


قیمت محصول بیشتر از ۱۰۰ هزار تومان است

یا:


موجودی محصول بیشتر از صفر است


۲. Candidate

Candidate همان شیئی است که می‌خواهیم بررسی کنیم.

مثلاً:


$user

یا:


$product

یا:


$order


۳. Composite Specification

گاهی یک قانون از چند قانون کوچک‌تر تشکیل شده است.

مثلاً:


Active User

AND

Premium User

AND

Minimum Purchase

در اینجا یک Specification ترکیبی داریم.

Specification Pattern معمولاً برای چنین ترکیب‌هایی از AND، OR و NOT استفاده می‌کند.


۴. isSatisfiedBy()

این متد مشخص می‌کند Candidate قانون را رعایت می‌کند یا خیر.

مثلاً:


public function isSatisfiedBy(User $user): bool

{

    return $user->active;

}

خروجی:


true

یا:


false


# پیاده‌سازی ساده Specification Pattern در PHP

بیایید یک مثال واقعی بسازیم.

فرض کنید یک کلاس User داریم:


<?php



declare(strict_types=1);



class User

{

    public function __construct(

        public string $name,

        public bool $active,

        public bool $premium,

        public int $purchaseCount

    ) {

    }

}

حالا Interface مربوط به Specification را می‌سازیم:


<?php



declare(strict_types=1);



interface Specification

{

    public function isSatisfiedBy(User $user): bool;

}

اکنون اولین Specification را می‌سازیم.


# ساخت ActiveUserSpecification


<?php



declare(strict_types=1);



final class ActiveUserSpecification implements Specification

{

    public function isSatisfiedBy(User $user): bool

    {

        return $user->active;

    }

}

این کلاس فقط یک مسئولیت دارد:

> بررسی کند آیا کاربر فعال است یا خیر.

استفاده:


$user = new User(

    name: 'Ali',

    active: true,

    premium: false,

    purchaseCount: 3

);



$specification = new ActiveUserSpecification();



if ($specification->isSatisfiedBy($user)) {

    echo 'User is active';

}


# ساخت PremiumUserSpecification

حالا قانون دوم:


<?php



declare(strict_types=1);



final class PremiumUserSpecification implements Specification

{

    public function isSatisfiedBy(User $user): bool

    {

        return $user->premium;

    }

}

و قانون سوم:


<?php



declare(strict_types=1);



final class MinimumPurchaseSpecification implements Specification

{

    public function __construct(

        private int $minimum

    ) {

    }



    public function isSatisfiedBy(User $user): bool

    {

        return $user->purchaseCount >= $this->minimum;

    }

}

حالا سه Specification مستقل داریم:


ActiveUserSpecification

PremiumUserSpecification

MinimumPurchaseSpecification


# ترکیب Specificationها با AND

اکنون فرض کنید قانون ما این است:


کاربر فعال باشد

AND

Premium باشد

می‌توانیم یک Specification ترکیبی ایجاد کنیم:


<?php



declare(strict_types=1);



final class AndSpecification implements Specification

{

    public function __construct(

        private Specification $first,

        private Specification $second

    ) {

    }



    public function isSatisfiedBy(User $user): bool

    {

        return

            $this->first->isSatisfiedBy($user)

            &&

            $this->second->isSatisfiedBy($user);

    }

}

اکنون:


$active = new ActiveUserSpecification();

$premium = new PremiumUserSpecification();



$specification = new AndSpecification(

    $active,

    $premium

);

و بررسی:


if ($specification->isSatisfiedBy($user)) {

    echo 'User is active and premium';

}


# ترکیب چند قانون با AND

حالا می‌توانیم قانون سوم را نیز اضافه کنیم:


Active

AND

Premium

AND

At least 5 purchases

مثلاً:


$active = new ActiveUserSpecification();



$premium = new PremiumUserSpecification();



$minimumPurchases = new MinimumPurchaseSpecification(5);



$activeAndPremium = new AndSpecification(

    $active,

    $premium

);



$finalSpecification = new AndSpecification(

    $activeAndPremium,

    $minimumPurchases

);

سپس:


if ($finalSpecification->isSatisfiedBy($user)) {

    echo 'User is eligible';

}

این همان ایده مهم Specification Pattern است:


Rule 1

   +

Rule 2

   +

Rule 3

   =

Complex Business Rule


# ترکیب Specificationها با OR

گاهی قانون به شکل زیر است:


کاربر Premium باشد

OR

بیش از ۱۰ خرید داشته باشد

برای این کار می‌توانیم OrSpecification داشته باشیم:


<?php



declare(strict_types=1);



final class OrSpecification implements Specification

{

    public function __construct(

        private Specification $first,

        private Specification $second

    ) {

    }



    public function isSatisfiedBy(User $user): bool

    {

        return

            $this->first->isSatisfiedBy($user)

            ||

            $this->second->isSatisfiedBy($user);

    }

}

حالا:


$premium = new PremiumUserSpecification();



$manyPurchases = new MinimumPurchaseSpecification(10);



$specification = new OrSpecification(

    $premium,

    $manyPurchases

);

و:


if ($specification->isSatisfiedBy($user)) {

    echo 'User is eligible';

}


# استفاده از NOT در Specification

گاهی می‌خواهیم نتیجه یک Specification را معکوس کنیم.

مثلاً:


کاربر Premium نیست

می‌توانیم NotSpecification بسازیم:


<?php



declare(strict_types=1);



final class NotSpecification implements Specification

{

    public function __construct(

        private Specification $specification

    ) {

    }



    public function isSatisfiedBy(User $user): bool

    {

        return !$this->specification->isSatisfiedBy($user);

    }

}

حالا:


$premium = new PremiumUserSpecification();



$notPremium = new NotSpecification($premium);

و:


if ($notPremium->isSatisfiedBy($user)) {

    echo 'User is not premium';

}


# یک مثال واقعی‌تر در فروشگاه اینترنتی

فرض کنید یک فروشگاه داریم و می‌خواهیم مشخص کنیم یک محصول برای یک کاربر قابل خرید با تخفیف ویژه است یا خیر.

قوانین:


کاربر فعال باشد

AND

کاربر حداقل 5 خرید داشته باشد

AND

کاربر Premium باشد

کلاس User:


<?php



declare(strict_types=1);



class User

{

    public function __construct(

        public int $id,

        public string $name,

        public bool $active,

        public bool $premium,

        public int $purchaseCount

    ) {

    }

}

Specificationها:


$active = new ActiveUserSpecification();



$premium = new PremiumUserSpecification();



$minimumPurchases = new MinimumPurchaseSpecification(5);

ترکیب:


$activeAndPremium = new AndSpecification(

    $active,

    $premium

);



$eligibleUser = new AndSpecification(

    $activeAndPremium,

    $minimumPurchases

);

حالا:


$user = new User(

    id: 1,

    name: 'Ali',

    active: true,

    premium: true,

    purchaseCount: 8

);



if ($eligibleUser->isSatisfiedBy($user)) {

    echo 'Special discount is available.';

}

مزیت مهم این ساختار این است که قانون تخفیف دیگر داخل Controller یا Service پراکنده نشده است.


# ساختار بهتر برای پروژه‌های واقعی

در پروژه واقعی بهتر است Specificationها را در پوشه‌ای مشخص قرار دهیم:


app/

├── Domain/

│   └── User/

│       ├── User.php

│       └── Specifications/

│           ├── Specification.php

│           ├── ActiveUserSpecification.php

│           ├── PremiumUserSpecification.php

│           ├── MinimumPurchaseSpecification.php

│           ├── AndSpecification.php

│           ├── OrSpecification.php

│           └── NotSpecification.php

این ساختار باعث می‌شود قوانین Domain از Controllerها و بخش‌های زیرساختی جدا بمانند.


# Specification Pattern در Laravel

Specification Pattern محدود به PHP خام نیست و می‌توان آن را در پروژه‌های Laravel نیز استفاده کرد.

فرض کنید مدل:


<?php



namespace App\Models;



use Illuminate\Database\Eloquent\Model;



class User extends Model

{

    protected $casts = [

        'active' => 'boolean',

        'premium' => 'boolean',

    ];

}

می‌توانیم Specification مربوط به کاربر فعال بسازیم:


<?php



namespace App\Domain\User\Specifications;



use App\Models\User;



final class ActiveUserSpecification

{

    public function isSatisfiedBy(User $user): bool

    {

        return $user->active === true;

    }

}

استفاده:


$specification = new ActiveUserSpecification();



if ($specification->isSatisfiedBy($user)) {

    // User is active

}


# Specification و Repository

یکی از کاربردهای مهم Specification ترکیب آن با Repository است.

فرض کنید Repository داریم:


interface UserRepository

{

    public function findEligibleUsers(

        Specification $specification

    ): array;

}

در این معماری، Repository مسئول دسترسی به داده است و Specification مسئول تعریف قانون.

این جداسازی اهمیت زیادی دارد.

به صورت مفهومی:


Specification

       ↓

Business Rule

       ↓

Repository

       ↓

Database

اما یک نکته مهم وجود دارد.

اگر isSatisfiedBy() را برای تک‌تک رکوردهای دیتابیس اجرا کنیم، ممکن است برای داده‌های بسیار زیاد کارایی مناسبی نداشته باشد.

برای مثال اگر یک میلیون کاربر داشته باشیم و ابتدا همه کاربران را از دیتابیس بگیریم و بعد Specification را در PHP اجرا کنیم، مقدار زیادی داده غیرضروری منتقل می‌شود.

بنابراین در پروژه‌های بزرگ بهتر است Specificationهای قابل تبدیل به Query، به شرط‌های دیتابیس ترجمه شوند. این موضوع در پیاده‌سازی‌های Query-oriented Specification نیز مطرح است.


# Specification و Eloquent

برای مثال می‌توانیم یک قانون را مستقیماً با Eloquent پیاده کنیم:


$users = User::query()

    ->where('active', true)

    ->where('premium', true)

    ->get();

اما این روش با Specification خالص متفاوت است.

در Specification خالص:


$specification->isSatisfiedBy($user);

تمرکز روی Business Rule است.

در Query:


User::query()

    ->where(...)

تمرکز روی نحوه دریافت داده از دیتابیس است.

بنابراین بهتر است Domain Specification را مستقیماً به Eloquent وابسته نکنیم، مخصوصاً اگر هدف ما معماری تمیز و قابل تست باشد.

یک معماری پیشرفته‌تر می‌تواند دو بخش داشته باشد:


Domain Specification

        ↓

Business Rule



Query Specification

        ↓

Eloquent / SQL

این تفکیک به جلوگیری از وابستگی منطق کسب‌وکار به ORM کمک می‌کند.


# تفاوت Specification با Strategy Pattern

Specification و Strategy ممکن است در نگاه اول شبیه یکدیگر باشند، اما هدف متفاوتی دارند.

Strategy

Strategy معمولاً می‌گوید:

> برای انجام یک کار، کدام الگوریتم را استفاده کنیم؟

مثلاً:


PaymentStrategy

├── ZarinpalPayment

├── StripePayment

└── PaypalPayment


Specification

Specification می‌گوید:

> آیا این Object شرایط مشخصی را دارد؟

مثلاً:


PremiumUser

ActiveUser

HighValueCustomer

و خروجی معمولاً:


true

یا:


false

بنابراین به صورت ساده:


Strategy

→ انتخاب الگوریتم



Specification

→ بررسی یک قانون


# تفاوت Specification با Validation

Validation نیز ممکن است شبیه Specification به نظر برسد.

مثلاً:


$request->validate([

    'email' => ['required', 'email'],

]);

این Validation مربوط به معتبر بودن ورودی است.

اما Specification بیشتر برای Business Rule استفاده می‌شود.

مثلاً:


آیا این مشتری واجد شرایط دریافت تخفیف VIP است؟

یا:


آیا این سفارش شرایط ارسال رایگان را دارد؟

یا:


آیا این کاربر اجازه خرید این محصول را دارد؟

پس بهتر است این دو مفهوم را یکی ندانیم.


# تفاوت Specification با Query Scope

در Laravel می‌توانیم Query Scope داشته باشیم:


public function scopeActive($query)

{

    return $query->where('active', true);

}

و:


User::active()->get();

Query Scope برای ساخت Queryهای قابل استفاده مجدد بسیار مناسب است.

اما Specification الزاماً Query مربوط به دیتابیس نیست.

ممکن است:


$specification->isSatisfiedBy($user);

روی یک Object موجود در حافظه اجرا شود.

بنابراین:


Specification

→ Business Rule



Query Scope

→ Database Query

البته در معماری‌های پیشرفته می‌توان بین Specification و Query Layer ارتباط برقرار کرد.


# مزایای Specification Pattern

۱. جدا شدن Business Logic

قوانین کسب‌وکار از Controller و Serviceهای شلوغ جدا می‌شوند.


۲. قابلیت استفاده مجدد

اگر یک قانون در چند قسمت استفاده شود، لازم نیست آن را چند بار بنویسیم.

مثلاً:


new ActiveUserSpecification();

می‌تواند در چند بخش مختلف استفاده شود.


۳. تست‌پذیری بالا

هر Specification یک کلاس مستقل است و می‌توان آن را جداگانه تست کرد.


۴. ترکیب قوانین

قوانین را می‌توان با:


AND

OR

NOT

ترکیب کرد.

این ویژگی یکی از مهم‌ترین مزیت‌های این الگو است.


۵. خوانایی بهتر Business Rule

مقایسه کنید:


if (

    $user->active &&

    $user->premium &&

    $user->purchaseCount >= 5

) {

}

با:


$eligibleUser->isSatisfiedBy($user);

در حالت دوم، نام Specification می‌تواند مفهوم قانون را منتقل کند.


# معایب Specification Pattern

Specification Pattern همیشه بهترین انتخاب نیست.

۱. افزایش تعداد کلاس‌ها

ممکن است برای چند شرط ساده، چند کلاس ایجاد کنیم:


ActiveUserSpecification

PremiumUserSpecification

VerifiedUserSpecification

اگر پروژه کوچک باشد، این ساختار ممکن است بیش از حد پیچیده شود.


۲. پیچیده شدن ترکیب Specificationها

اگر Specificationها بیش از حد تو در تو شوند:


new AndSpecification(

    new OrSpecification(

        $a,

        $b

    ),

    new NotSpecification(

        $c

    )

);

خواندن آن سخت می‌شود.

در این حالت بهتر است ساختار ترکیب را ساده‌تر یا با نام‌گذاری مناسب‌تر پیاده‌سازی کنیم.


۳. مشکل در ترجمه به SQL

یک Specification که در PHP کار می‌کند، الزاماً به‌سادگی قابل تبدیل به SQL نیست.

مثلاً:


return $user->getScore() > 100;

ممکن است به سادگی به یک Query تبدیل نشود، مخصوصاً اگر منطق داخل PHP پیچیده باشد.

برای Datasetهای بزرگ، باید این مسئله از ابتدا در طراحی معماری در نظر گرفته شود.


# چه زمانی از Specification Pattern استفاده کنیم؟

این الگو زمانی ارزش بیشتری پیدا می‌کند که:

  • Business Ruleها پیچیده باشند.
  • قوانین در چند قسمت پروژه استفاده شوند.
  • قوانین نیاز به ترکیب داشته باشند.
  • بخواهیم قوانین را جداگانه تست کنیم.
  • پروژه Domain Logic قابل توجهی داشته باشد.
  • بخواهیم منطق کسب‌وکار از Infrastructure جدا باشد.

برای مثال در یک سیستم فروشگاهی:


IsActive

IsPremium

HasMinimumPurchase

HasEnoughCredit

EligibleForDiscount

EligibleForFreeShipping

این‌ها کاندیدهای خوبی برای تبدیل شدن به Specification هستند.


# چه زمانی از Specification Pattern استفاده نکنیم؟

اگر فقط یک شرط ساده داریم:


if ($user->active) {

}

احتمالاً نیازی به ایجاد:


ActiveUserSpecification

نداریم.

همچنین اگر پروژه بسیار کوچک است و Business Rule پیچیده‌ای ندارد، استفاده بیش از حد از Patternها می‌تواند پیچیدگی اضافی ایجاد کند.

Design Patternها قرار نیست همیشه استفاده شوند؛ انتخاب Pattern باید بر اساس مسئله واقعی پروژه انجام شود. مستندات DesignPatternsPHP نیز روی توجه به زمان و trade-offهای هر Pattern تأکید می‌کند.


# تست Specification با PHPUnit

یکی از نقاط قوت این الگو تست‌پذیری آن است.

مثلاً:


<?php



use PHPUnit\Framework\TestCase;



final class ActiveUserSpecificationTest extends TestCase

{

    public function test_active_user_satisfies_specification(): void

    {

        $user = new User(

            name: 'Ali',

            active: true,

            premium: false,

            purchaseCount: 2

        );



        $specification = new ActiveUserSpecification();



        self::assertTrue(

            $specification->isSatisfiedBy($user)

        );

    }



    public function test_inactive_user_does_not_satisfy_specification(): void

    {

        $user = new User(

            name: 'Ali',

            active: false,

            premium: false,

            purchaseCount: 2

        );



        $specification = new ActiveUserSpecification();



        self::assertFalse(

            $specification->isSatisfiedBy($user)

        );

    }

}

در این حالت تست بسیار ساده است:


Input

  ↓

Specification

  ↓

true / false

نمونه‌های Specification در DesignPatternsPHP نیز شامل تست‌هایی برای Specificationهای ساده و ترکیبی هستند.


# یک ساختار حرفه‌ای‌تر برای Specification

در PHP مدرن می‌توانیم Interface را عمومی‌تر طراحی کنیم:


<?php



declare(strict_types=1);



interface Specification

{

    public function isSatisfiedBy(mixed $candidate): bool;

}

و سپس:


final class ActiveUserSpecification implements Specification

{

    public function isSatisfiedBy(mixed $candidate): bool

    {

        if (!$candidate instanceof User) {

            return false;

        }



        return $candidate->active;

    }

}

البته در پروژه‌های واقعی بهتر است تا حد امکان Type Safety را حفظ کنیم و اگر Specification فقط برای User است، نوع User را مستقیماً در قرارداد یا ساختار مناسب Domain مشخص کنیم.


# Specification Pattern و Domain-Driven Design

Specification ارتباط نزدیکی با مدل‌سازی قوانین Domain دارد.

فرض کنید در یک سیستم اشتراک:


Premium Customer

Active Subscription

Paid Invoice

Eligible Customer

هرکدام می‌توانند یک مفهوم مشخص در Domain باشند.

به جای اینکه قوانین در تمام پروژه پراکنده شوند، می‌توانیم آن‌ها را به اشیای مشخص تبدیل کنیم.

مثلاً:


$eligibleCustomer = new AndSpecification(

    new ActiveCustomerSpecification(),

    new PremiumCustomerSpecification()

);

این رویکرد با ایده قرار دادن Business Rules در اشیای قابل ترکیب و تست‌پذیر همخوانی دارد.


# یک مثال کامل‌تر از ساختار پروژه

ساختار پیشنهادی:


app/

├── Domain/

│   └── Customer/

│       ├── Customer.php

│       └── Specifications/

│           ├── Specification.php

│           ├── ActiveCustomer.php

│           ├── PremiumCustomer.php

│           ├── MinimumPurchase.php

│           ├── AndSpecification.php

│           ├── OrSpecification.php

│           └── NotSpecification.php

│

├── Application/

│   └── Customer/

│       └── DiscountService.php

│

└── Infrastructure/

    └── Persistence/

        └── EloquentCustomerRepository.php

در این ساختار:


Domain

→ Business Rules



Application

→ Use Cases



Infrastructure

→ Database / Eloquent

این تفکیک کمک می‌کند Business Logic کمتر به Laravel یا دیتابیس وابسته باشد.


# یک نکته مهم درباره Specification و دیتابیس

یکی از اشتباهات رایج این است که تصور کنیم Specification همیشه باید جایگزین Query Builder شود.

این موضوع درست نیست.

مثلاً اگر یک میلیون محصول داشته باشیم:


$products = Product::all();



foreach ($products as $product) {

    if ($specification->isSatisfiedBy($product)) {

        // ...

    }

}

این روش می‌تواند بسیار پرهزینه باشد.

اگر شرط قابل تبدیل به SQL باشد، بهتر است فیلتر در خود دیتابیس انجام شود:


Product::query()

    ->where('active', true)

    ->where('price', '>=', 100000)

    ->get();

در معماری‌های پیشرفته‌تر می‌توان Specification را به یک Query Specification تبدیل کرد تا منطق شرطی در دیتابیس اجرا شود، نه روی تمام Objectهای بارگذاری‌شده در PHP.


# اشتباهات رایج در استفاده از Specification Pattern

اشتباه اول: استفاده برای هر شرط ساده

برای چنین چیزی:


if ($user->active) {

}

معمولاً Specification لازم نیست.


اشتباه دوم: وابسته کردن Domain به Eloquent

مثلاً:


final class ActiveUserSpecification

{

    public function isSatisfiedBy(User $user): bool

    {

        return User::query()

            ->where(...)

            ->exists();

    }

}

این طراحی باعث می‌شود Specification به دیتابیس و ORM وابسته شود.


اشتباه سوم: قرار دادن SQL داخل Specification

Specification باید بیشتر درباره:


Business Rule

صحبت کند، نه:


SQL Query

اگر نیاز به Query-level implementation دارید، بهتر است یک لایه جدا برای ترجمه Specification به Query داشته باشید.


اشتباه چهارم: ساخت Specificationهای بسیار بزرگ

اگر یک کلاس چنین کاری انجام دهد:


class UserEligibilitySpecification

{

    public function isSatisfiedBy(User $user): bool

    {

        // 100 lines

    }

}

احتمالاً بهتر است آن را به چند Rule کوچک‌تر تقسیم کنیم.

مثلاً:


ActiveUser

VerifiedUser

PremiumUser

MinimumPurchase

HasCredit

و سپس آن‌ها را ترکیب کنیم.


# جمع‌بندی

Specification Pattern در PHP روشی برای تبدیل Business Ruleها به اشیای مستقل، قابل استفاده مجدد و قابل ترکیب است.

هسته اصلی آن معمولاً متدی مانند:


isSatisfiedBy()

است که مشخص می‌کند یک Object شرایط Specification را دارد یا خیر.

مزیت مهم این Pattern زمانی مشخص می‌شود که چند قانون کسب‌وکار داشته باشیم و بخواهیم آن‌ها را ترکیب کنیم:


AND

OR

NOT

برای مثال:


$specification = new AndSpecification(

    new ActiveUserSpecification(),

    new PremiumUserSpecification()

);

سپس:


if ($specification->isSatisfiedBy($user)) {

    // ...

}

در پروژه‌های Laravel نیز می‌توان از این الگو برای جدا کردن Business Logic از Controller، Service و لایه Persistence استفاده کرد.

با این حال نباید برای هر شرط ساده‌ای Specification بسازیم. این الگو زمانی ارزش بیشتری دارد که قوانین کسب‌وکار تکرارشونده، پیچیده، قابل ترکیب و نیازمند تست مستقل باشند.


# سوالات متداول

Specification Pattern در PHP چیست؟

Specification Pattern یک الگوی طراحی برای کپسوله‌کردن Business Ruleها در قالب Objectهای مستقل است. این Objectها معمولاً بررسی می‌کنند آیا یک Candidate شرایط مشخصی را دارد یا خیر.


Specification Pattern چه مشکلی را حل می‌کند؟

این Pattern کمک می‌کند Business Ruleهای پیچیده از ifهای پراکنده خارج شوند و به کلاس‌های مستقل، قابل تست و قابل استفاده مجدد تبدیل شوند.


آیا Specificationها را می‌توان با AND و OR ترکیب کرد؟

بله. یکی از ویژگی‌های مهم Specification Pattern امکان ترکیب چند Specification با منطق‌هایی مانند AND، OR و NOT است.


آیا Specification Pattern در Laravel قابل استفاده است؟

بله. می‌توان Specificationها را در Domain یا Application Layer پروژه Laravel قرار داد و از آن‌ها در Serviceها و Repositoryها استفاده کرد.


تفاوت Specification با Strategy چیست؟

Strategy برای انتخاب یا تغییر الگوریتم انجام یک عملیات استفاده می‌شود، در حالی که Specification معمولاً یک Business Rule را بیان می‌کند و مشخص می‌کند آیا یک Object شرایط آن Rule را دارد یا خیر.


آیا Specification Pattern برای Query دیتابیس مناسب است؟

می‌تواند مناسب باشد، اما باید مراقب Performance بود. اجرای Specification روی تعداد زیادی Object در PHP می‌تواند پرهزینه باشد. برای داده‌های بزرگ بهتر است در صورت امکان، شرط‌ها به Query دیتابیس ترجمه شوند.


آیا Specification Pattern همان Validation است؟

خیر. Validation معمولاً برای بررسی معتبر بودن ورودی یا داده استفاده می‌شود، در حالی که Specification بیشتر برای مدل‌سازی Business Ruleها و شرایط Domain کاربرد دارد.


آیا Specification Pattern بخشی از Design Patternهای PHP است؟

Specification در مجموعه DesignPatternsPHP در دسته Behavioral Patterns فهرست شده و نمونه‌ای از پیاده‌سازی آن با isSatisfiedBy() ارائه شده است.


نتیجه نهایی

اگر پروژه شما پر از شرط‌های تکراری و Business Ruleهای پیچیده شده است، به جای اینکه مرتب ifهای بیشتری به کد اضافه کنید، می‌توانید هر قانون را به یک Specification تبدیل کرده و سپس آن‌ها را با یکدیگر ترکیب کنید.

به این ترتیب:


Business Rule

      ↓

Specification

      ↓

AND / OR / NOT

      ↓

Complex Business Rule

      ↓

Application

این ساختار به‌خصوص در پروژه‌های بزرگ PHP، Laravel، سیستم‌های فروشگاهی، سیستم‌های اشتراکی و پروژه‌هایی که Domain Logic زیادی دارند، می‌تواند کاربردی باشد.

نظرات (0)

نظرات پس از تأیید مدیر نمایش داده می‌شوند

هنوز نظری ثبت نشده است؛ اولین نفر باشید!

مقالات مرتبط

ساخت CMS با Laravel 12؛ آموزش ساخت سیستم مدیریت محتوا از صفر

ساخت CMS با Laravel 12؛ آموزش ساخت سیستم مدیریت محتوا از صفر

۲۴ دقیقه
ساخت فروشگاه اینترنتی با Laravel 12؛ آموزش پروژه‌محور از صفر تا طراحی هسته فروشگاه

ساخت فروشگاه اینترنتی با Laravel 12؛ آموزش پروژه‌محور از صفر تا طراحی هسته فروشگاه

۲۲ دقیقه
ساخت Marketplace با Laravel؛ آموزش طراحی فروشگاه چندفروشندگی

ساخت Marketplace با Laravel؛ آموزش طراحی فروشگاه چندفروشندگی

۲۲ دقیقه
مدیریت Queueهای سنگین در Laravel؛ آموزش پردازش کارهای طولانی

مدیریت Queueهای سنگین در Laravel؛ آموزش پردازش کارهای طولانی

۲۲ دقیقه