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

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

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

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

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

در پروژه‌های واقعی PHP و Laravel، معمولاً با قوانین و شرط‌های زیادی مواجه می‌شویم.

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

  • آیا محصول فعال است؟
  • آیا موجودی محصول بیشتر از صفر است؟
  • آیا قیمت محصول کمتر از یک مقدار مشخص است؟
  • آیا کاربر اجازه خرید محصول را دارد؟
  • آیا سفارش قابل لغو است؟
  • آیا کاربر شرایط دریافت تخفیف را دارد؟

در یک پروژه ساده ممکن است این شرایط را مستقیماً با if و else پیاده‌سازی کنیم:


if (

    $product->isActive()

    && $product->getStock() > 0

    && $product->getPrice() < 10000000

) {

    // ...

}

اما وقتی تعداد قوانین بیشتر شود، کد به‌سرعت پیچیده خواهد شد.

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

اینجاست که Specification Pattern می‌تواند کمک کند.

Specification Pattern به ما اجازه می‌دهد یک قانون کسب‌وکار را به یک Object مستقل تبدیل کنیم؛ سپس می‌توانیم آن قانون را مجدداً استفاده، تست و با قوانین دیگر ترکیب کنیم. در پیاده‌سازی‌های رایج این Pattern، متدی مانند isSatisfiedBy() مشخص می‌کند که آیا یک Object شرایط Specification را دارد یا خیر.


# Specification Pattern چیست؟

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

به زبان ساده، به جای اینکه شرط را در قسمت‌های مختلف برنامه تکرار کنیم، آن را در یک کلاس قرار می‌دهیم.

مثلاً به جای:


if ($product->getPrice() <= 10000000) {

    // ...

}

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


$specification->isSatisfiedBy($product);

این روش باعث می‌شود قانون موردنظر یک نام مشخص داشته باشد و بتوانیم آن را در قسمت‌های مختلف برنامه استفاده کنیم.

DesignPatternsPHP نیز Specification را با همین ایده معرفی می‌کند: ساخت Specification برای قوانین کسب‌وکار و بررسی اینکه یک Object شرایط آن قانون را دارد یا خیر.


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

فرض کنید در یک فروشگاه قانون زیر را داریم:

> فقط محصولاتی که فعال هستند، موجودی دارند و قیمت آن‌ها کمتر از ۱۰ میلیون تومان است قابل خرید هستند.

یک پیاده‌سازی ساده ممکن است چنین چیزی باشد:


if (

    $product->isActive()

    && $product->getStock() > 0

    && $product->getPrice() <= 10000000

) {

    // محصول قابل خرید است

}

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


ProductController

OrderService

CartService

CheckoutService

AdminService

احتمالاً این شرط در چند قسمت پروژه تکرار خواهد شد.

مشکل بعدی این است که اگر قانون تغییر کند، مثلاً حداکثر قیمت از ۱۰ میلیون به ۱۵ میلیون تغییر کند، باید چند قسمت مختلف کد را پیدا و اصلاح کنیم.

Specification پیشنهاد می‌کند این قانون را به یک Object تبدیل کنیم:


PurchasableProductSpecification

حالا هر جا لازم باشد فقط می‌توانیم بنویسیم:


$specification->isSatisfiedBy($product);


# ساختار Specification Pattern

ساختار ساده Specification را می‌توان این‌گونه تصور کرد:


                Specification

                      │

                      │

                isSatisfiedBy()

                      │

                      ↓

                   Product

Specification یک قرارداد مشخص دارد:


interface SpecificationInterface

{

    public function isSatisfiedBy(object $item): bool;

}

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

مثلاً:


ActiveProductSpecification

PriceSpecification

StockSpecification

PremiumUserSpecification

AvailableOrderSpecification


# Interface مربوط به Specification

ابتدا Interface اصلی را ایجاد می‌کنیم:


interface SpecificationInterface

{

    public function isSatisfiedBy(object $item): bool;

}

مهم‌ترین قسمت این Interface متد زیر است:


isSatisfiedBy()

این متد باید مشخص کند که آیا Object موردنظر شرایط Specification را دارد یا خیر.

مثلاً:


$result = $specification->isSatisfiedBy($product);

نتیجه می‌تواند:


true

یا:


false

باشد.


# ساخت کلاس Product

برای مثال ابتدا یک Product ساده ایجاد کنیم:


class Product

{

    public function __construct(

        private string $name,

        private float $price,

        private int $stock,

        private bool $active

    ) {

    }



    public function getName(): string

    {

        return $this->name;

    }



    public function getPrice(): float

    {

        return $this->price;

    }



    public function getStock(): int

    {

        return $this->stock;

    }



    public function isActive(): bool

    {

        return $this->active;

    }

}

حالا یک محصول داریم که چهار ویژگی اصلی دارد:


name

price

stock

active


# ساخت ActiveProductSpecification

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


class ActiveProductSpecification implements SpecificationInterface

{

    public function isSatisfiedBy(object $item): bool

    {

        return $item instanceof Product

            && $item->isActive();

    }

}

اکنون می‌توانیم بنویسیم:


$product = new Product(

    'Laptop',

    50000000,

    10,

    true

);



$specification = new ActiveProductSpecification();



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

    echo 'Product is active.';

}

در اینجا قانون فعال بودن محصول دیگر در Controller یا Service قرار ندارد.

قانون در کلاس خودش قرار گرفته است.


# ساخت PriceSpecification

حالا می‌خواهیم Specification دیگری ایجاد کنیم که حداکثر قیمت محصول را بررسی کند.


class PriceSpecification implements SpecificationInterface

{

    public function __construct(

        private float $maxPrice

    ) {

    }



    public function isSatisfiedBy(object $item): bool

    {

        return $item instanceof Product

            && $item->getPrice() <= $this->maxPrice;

    }

}

حالا می‌توانیم یک Specification بسازیم:


$priceSpecification = new PriceSpecification(

    10000000

);

و آن را بررسی کنیم:


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

    echo 'Price is acceptable.';

}


# ساخت StockSpecification

حالا قانون موجودی را ایجاد کنیم:


class StockSpecification implements SpecificationInterface

{

    public function isSatisfiedBy(object $item): bool

    {

        return $item instanceof Product

            && $item->getStock() > 0;

    }

}

اکنون سه قانون مستقل داریم:


ActiveProductSpecification

PriceSpecification

StockSpecification

هر کدام فقط یک قانون را مدیریت می‌کنند.


# مشکل جدید چیست؟

حالا سه Specification داریم.

اما برای خرید محصول باید هر سه شرط برقرار باشند.

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


if (

    $activeSpecification->isSatisfiedBy($product)

    && $priceSpecification->isSatisfiedBy($product)

    && $stockSpecification->isSatisfiedBy($product)

) {

    // Product can be purchased

}

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

ما دوباره منطق ترکیب قوانین را در کد اصلی نوشته‌ایم.

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

اینجاست که Composite Specification وارد می‌شود.


# Composite Specification چیست؟

Composite Specification به ما اجازه می‌دهد چند Specification را با یکدیگر ترکیب کنیم.

مثلاً:


Active

   AND

In Stock

   AND

Price <= 10M

و در نهایت فقط یک Specification داشته باشیم:


PurchasableProductSpecification

این یکی از بخش‌های مهم Specification Pattern است؛ Specificationها می‌توانند با منطق‌هایی مانند AND، OR و NOT ترکیب شوند. نمونه‌های DesignPatternsPHP نیز ترکیب Specificationها و Specificationهای not و plus را نشان می‌دهند.


# ساخت AndSpecification

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


class AndSpecification implements SpecificationInterface

{

    public function __construct(

        private SpecificationInterface $first,

        private SpecificationInterface $second

    ) {

    }



    public function isSatisfiedBy(object $item): bool

    {

        return $this->first->isSatisfiedBy($item)

            && $this->second->isSatisfiedBy($item);

    }

}

حالا می‌توانیم دو قانون را با هم ترکیب کنیم:


$activeAndStock = new AndSpecification(

    new ActiveProductSpecification(),

    new StockSpecification()

);

حالا:


$activeAndStock->isSatisfiedBy($product);

فقط زمانی true خواهد بود که هر دو قانون برقرار باشند.


# ترکیب سه Specification

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


$purchasable = new AndSpecification(

    $activeAndStock,

    new PriceSpecification(10000000)

);

ساختار ما اکنون:


                Purchasable

                    │

                   AND

                 /     \

              AND      Price

             /   \

         Active   Stock

است.

حالا فقط یک شرط داریم:


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

    echo 'Product can be purchased.';

}

این دقیقاً یکی از مزیت‌های اصلی Specification Pattern است: می‌توان قوانین ساده را به Specificationهای بزرگ‌تر و قابل استفاده مجدد ترکیب کرد.


# ساخت OrSpecification

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

مثلاً:

> کاربر یا باید Premium باشد یا حداقل ۱۰ خرید موفق داشته باشد.

برای چنین شرایطی می‌توان OrSpecification ساخت:


class OrSpecification implements SpecificationInterface

{

    public function __construct(

        private SpecificationInterface $first,

        private SpecificationInterface $second

    ) {

    }



    public function isSatisfiedBy(object $item): bool

    {

        return $this->first->isSatisfiedBy($item)

            || $this->second->isSatisfiedBy($item);

    }

}

اکنون:


$specification = new OrSpecification(

    $firstSpecification,

    $secondSpecification

);

اگر یکی از آن‌ها true باشد، نتیجه true خواهد بود.


# ساخت NotSpecification

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

برای مثال:


Product is NOT out of stock

می‌توانیم کلاس زیر را ایجاد کنیم:


class NotSpecification implements SpecificationInterface

{

    public function __construct(

        private SpecificationInterface $specification

    ) {

    }



    public function isSatisfiedBy(object $item): bool

    {

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

    }

}

حالا:


$notOutOfStock = new NotSpecification(

    $outOfStockSpecification

);

و نتیجه برعکس Specification اصلی خواهد بود.


# یک مثال واقعی‌تر از Specification Pattern

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

قوانین خرید محصول:

  1. محصول فعال باشد.
  2. محصول موجود باشد.
  3. قیمت کمتر از ۲۰ میلیون باشد.

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


ActiveProductSpecification

StockSpecification

MaxPriceSpecification

سپس:


$specification = new AndSpecification(

    new ActiveProductSpecification(),

    new AndSpecification(

        new StockSpecification(),

        new MaxPriceSpecification(20000000)

    )

);

اکنون در Service فقط داریم:


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

    // خرید محصول مجاز است

}

Service دیگر لازم نیست جزئیات قوانین را بداند.


# یک ساختار بهتر برای Specificationها

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


app/

├── Specifications/

│   ├── SpecificationInterface.php

│   ├── AndSpecification.php

│   ├── OrSpecification.php

│   ├── NotSpecification.php

│   ├── ActiveProductSpecification.php

│   ├── StockSpecification.php

│   └── MaxPriceSpecification.php

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


# Specification Pattern در Laravel

Specification Pattern در Laravel می‌تواند کاربرد زیادی داشته باشد.

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

به جای اینکه Controller را با شرط‌های مختلف پر کنیم:


if (

    $product->active

    && $product->stock > 0

    && $product->price <= 20000000

) {

    // ...

}

می‌توانیم Specification ایجاد کنیم.

مثلاً:


class PurchasableProductSpecification

    implements SpecificationInterface

{

    public function isSatisfiedBy(object $item): bool

    {

        return $item instanceof Product

            && $item->active

            && $item->stock > 0

            && $item->price <= 20000000;

    }

}

حالا در Service:


if (

    $specification->isSatisfiedBy($product)

) {

    // Purchase

}

این روش باعث می‌شود Controller یا Service بیش از حد درگیر قوانین کسب‌وکار نشود.


# استفاده از Specification برای کاربران

Specification فقط برای Product نیست.

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

می‌خواهیم بررسی کنیم:

> آیا کاربر اجازه استفاده از یک قابلیت خاص را دارد؟

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


class PremiumUserSpecification

    implements SpecificationInterface

{

    public function isSatisfiedBy(object $item): bool

    {

        return $item instanceof User

            && $item->isPremium();

    }

}

حالا:


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

    // Allow feature

}


# ترکیب قوانین کاربران

فرض کنید کاربر باید:

  • Premium باشد
  • حسابش فعال باشد

در این حالت:


$specification = new AndSpecification(

    new PremiumUserSpecification(),

    new ActiveUserSpecification()

);

سپس:


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

    // User can access feature

}

اگر بعداً شرط دیگری اضافه شود، می‌توانیم Specification جدیدی ایجاد کنیم و آن را به ساختار اضافه کنیم.


# Specification برای سفارش‌ها

یک مثال بسیار کاربردی دیگر، سیستم سفارش است.

فرض کنید فقط سفارش‌هایی که:

  • وضعیت pending دارند
  • مبلغشان بیشتر از ۱ میلیون تومان است
  • پرداخت نشده‌اند

قابل پردازش هستند.

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


PendingOrderSpecification

MinimumAmountSpecification

UnpaidOrderSpecification

و سپس:


$processableOrder = new AndSpecification(

    new PendingOrderSpecification(),

    new AndSpecification(

        new MinimumAmountSpecification(1000000),

        new UnpaidOrderSpecification()

    )

);

در Service:


if ($processableOrder->isSatisfiedBy($order)) {

    // Process order

}

این ساختار در پروژه‌های بزرگ می‌تواند بسیار خواناتر از مجموعه‌ای از ifهای تو در تو باشد.


# Specification و Query در Laravel

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

Specification Pattern را می‌توان برای بررسی Object موجود در حافظه استفاده کرد، اما در پروژه‌های Laravel ممکن است بخواهیم همین قوانین را مستقیماً روی Query Builder یا Eloquent نیز اعمال کنیم.

برای مثال:


Product::query()

    ->where('active', true)

    ->where('stock', '>', 0)

    ->where('price', '<=', 20000000)

    ->get();

این موضوع را باید از Specification مربوط به Objectها جدا در نظر گرفت.

اگر هدف شما ساخت قوانین قابل ترکیب برای Queryها باشد، می‌توان یک لایه Specification طراحی کرد که به جای bool، Query را توسعه دهد.

در منابع جدید PHP نیز Specification Pattern برای تبدیل منطق فیلتر و Query به اجزای قابل ترکیب و قابل استفاده مجدد مطرح شده است.


# تفاوت Business Specification و Query Specification

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

Business Specification

هدف:


آیا این Object شرایط را دارد؟

خروجی:


true / false

مثلاً:


$specification->isSatisfiedBy($product);

Query Specification

هدف:


چه رکوردهایی شرایط را دارند؟

مثلاً:


Product::query()

    ->where(...)

    ->where(...)

    ->get();

هر دو از ایده Specification استفاده می‌کنند، اما نحوه اجرای آن‌ها متفاوت است.


# تفاوت Specification و Strategy

در مقاله‌های قبلی با Strategy Pattern آشنا شدیم.

Strategy برای انتخاب و تعویض الگوریتم‌ها استفاده می‌شود.

مثلاً:


PaymentStrategy

    ├── ZarinpalPayment

    ├── StripePayment

    └── PaypalPayment

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

مثلاً:


ActiveUserSpecification

PremiumUserSpecification

HighValueCustomerSpecification

به صورت ساده:


Strategy

→ چگونه یک کار را انجام دهیم؟



Specification

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


# تفاوت Specification و Chain of Responsibility

Chain of Responsibility نیز می‌تواند با شرط‌ها کار کند، اما هدف آن متفاوت است.

در Chain of Responsibility درخواست از یک زنجیره Handler عبور می‌کند:


Request

   ↓

Handler 1

   ↓

Handler 2

   ↓

Handler 3

اما Specification معمولاً روی یک Object بررسی انجام می‌دهد:


Product

   ↓

Specification

   ↓

true / false

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


# مزایای Specification Pattern

۱. جداسازی قوانین کسب‌وکار

به جای قرار دادن تمام قوانین در Controller و Service، هر قانون می‌تواند کلاس خودش را داشته باشد.


۲. استفاده مجدد از قوانین

یک Specification را می‌توان در قسمت‌های مختلف پروژه استفاده کرد.

مثلاً:


$activeProductSpecification

می‌تواند در:


CartService

CheckoutService

OrderService

استفاده شود.


۳. امکان ترکیب قوانین

می‌توان Specificationها را با:


AND

OR

NOT

ترکیب کرد.


۴. تست آسان‌تر

هر Business Rule می‌تواند به صورت مستقل تست شود.

مثلاً:


$this->assertTrue(

    $specification->isSatisfiedBy($product)

);


۵. خوانایی بهتر کد

این:


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

    // ...

}

معمولاً از شرط‌های طولانی و چندلایه خواناتر است.


# معایب Specification Pattern

Specification نیز مانند هر Design Pattern دیگری همیشه بهترین گزینه نیست.

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

ممکن است برای هر قانون یک کلاس ایجاد شود:


ActiveSpecification

StockSpecification

PriceSpecification

UserSpecification

OrderSpecification

...

در پروژه‌های کوچک این موضوع می‌تواند بیش از حد پیچیدگی ایجاد کند.


۲. احتمال بیش‌ازحد انتزاعی شدن کد

اگر برای یک شرط بسیار ساده مانند:


$user->active

یک کلاس کامل ایجاد کنیم، احتمالاً طراحی بیش از حد پیچیده شده است.


۳. نیاز به طراحی مناسب برای Queryها

اگر Specification قرار است مستقیماً روی Eloquent Queryها اعمال شود، باید معماری آن را از ابتدا به شکل مناسبی طراحی کنیم.


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

Specification انتخاب مناسبی است زمانی که:

  • قوانین کسب‌وکار متعدد دارید.
  • قوانین در چند قسمت پروژه استفاده می‌شوند.
  • قوانین مرتباً تغییر می‌کنند.
  • نیاز به ترکیب چند شرط دارید.
  • می‌خواهید قوانین را به صورت مستقل تست کنید.
  • Controller یا Service دارای شرط‌های بسیار زیاد شده است.
  • پروژه شما Domain Logic قابل توجهی دارد.

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

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


if ($user->active) {

    // ...

}

ساخت یک Specification کامل احتمالاً ضرورتی ندارد.

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

نکته مهم در Design Patternها این است که صرفاً به دلیل وجود یک Pattern نباید آن را وارد پروژه کنیم؛ باید ببینیم چه مشکلی را حل می‌کند و هزینه و مزیت آن چیست. خود پروژه DesignPatternsPHP نیز روی اهمیت انتخاب درست Pattern و توجه به trade-offها تأکید می‌کند.


# تست Specification با PHPUnit

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

مثلاً Specification قیمت:


class PriceSpecification implements SpecificationInterface

{

    public function __construct(

        private float $maxPrice

    ) {

    }



    public function isSatisfiedBy(object $item): bool

    {

        return $item instanceof Product

            && $item->getPrice() <= $this->maxPrice;

    }

}

حالا تست:


use PHPUnit\Framework\TestCase;



class PriceSpecificationTest extends TestCase

{

    public function test_product_price_is_acceptable(): void

    {

        $product = new Product(

            'Laptop',

            5000000,

            10,

            true

        );



        $specification = new PriceSpecification(

            10000000

        );



        $this->assertTrue(

            $specification->isSatisfiedBy($product)

        );

    }



    public function test_product_price_is_not_acceptable(): void

    {

        $product = new Product(

            'Laptop',

            15000000,

            10,

            true

        );



        $specification = new PriceSpecification(

            10000000

        );



        $this->assertFalse(

            $specification->isSatisfiedBy($product)

        );

    }

}

اکنون Business Rule به صورت کاملاً مستقل تست شده است.

نمونه‌های DesignPatternsPHP نیز برای Specification تست‌هایی جهت بررسی نتیجه isSatisfiedBy() و ترکیب Specificationها ارائه می‌کنند.


# مثال کامل Specification Pattern در PHP

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


<?php



interface SpecificationInterface

{

    public function isSatisfiedBy(object $item): bool;

}



class Product

{

    public function __construct(

        private string $name,

        private float $price,

        private int $stock,

        private bool $active

    ) {

    }



    public function getName(): string

    {

        return $this->name;

    }



    public function getPrice(): float

    {

        return $this->price;

    }



    public function getStock(): int

    {

        return $this->stock;

    }



    public function isActive(): bool

    {

        return $this->active;

    }

}



class ActiveProductSpecification

    implements SpecificationInterface

{

    public function isSatisfiedBy(object $item): bool

    {

        return $item instanceof Product

            && $item->isActive();

    }

}



class StockSpecification

    implements SpecificationInterface

{

    public function isSatisfiedBy(object $item): bool

    {

        return $item instanceof Product

            && $item->getStock() > 0;

    }

}



class PriceSpecification

    implements SpecificationInterface

{

    public function __construct(

        private float $maxPrice

    ) {

    }



    public function isSatisfiedBy(object $item): bool

    {

        return $item instanceof Product

            && $item->getPrice() <= $this->maxPrice;

    }

}



class AndSpecification

    implements SpecificationInterface

{

    public function __construct(

        private SpecificationInterface $first,

        private SpecificationInterface $second

    ) {

    }



    public function isSatisfiedBy(object $item): bool

    {

        return $this->first->isSatisfiedBy($item)

            && $this->second->isSatisfiedBy($item);

    }

}



$product = new Product(

    'Laptop',

    8000000,

    5,

    true

);



$active = new ActiveProductSpecification();



$stock = new StockSpecification();



$price = new PriceSpecification(

    10000000

);



$activeAndStock = new AndSpecification(

    $active,

    $stock

);



$purchasable = new AndSpecification(

    $activeAndStock,

    $price

);



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

    echo 'Product can be purchased.';

} else {

    echo 'Product cannot be purchased.';

}

در این مثال، سه قانون مستقل داریم:


Active

Stock

Price

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


Purchasable

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


                Purchasable

                     │

                    AND

                   /   \

                AND     Price

               /   \

          Active    Stock

این همان ایده اصلی Specification Pattern است: قوانین کسب‌وکار را به Objectهای قابل ترکیب تبدیل کنیم.


# Specification Pattern به زبان ساده

اگر بخواهیم کل Pattern را خیلی ساده بیان کنیم:

به جای اینکه در تمام پروژه بنویسیم:


if (

    $product->active

    && $product->stock > 0

    && $product->price <= 10000000

) {

    // ...

}

قوانین را تبدیل می‌کنیم به:


ActiveProductSpecification

StockSpecification

PriceSpecification

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


Active

   AND

Stock

   AND

Price

در نهایت فقط یک سؤال از Specification می‌پرسیم:


$purchasable->isSatisfiedBy($product);

نتیجه:


true

یا:


false

خواهد بود.


# جمع‌بندی Specification Pattern در PHP

Specification Pattern یکی از Patternهای کاربردی برای مدیریت Business Ruleها در پروژه‌های PHP است.

ایده اصلی آن ساده است:

> هر قانون کسب‌وکار را به یک Object مستقل تبدیل کنیم تا بتوانیم آن را استفاده مجدد، تست و با قوانین دیگر ترکیب کنیم.

مهم‌ترین بخش‌های این Pattern عبارت‌اند از:


Specification

isSatisfiedBy()

Concrete Specification

Composite Specification

AND

OR

NOT

این Pattern در پروژه‌های کوچک ممکن است غیرضروری باشد، اما در پروژه‌هایی که قوانین کسب‌وکار زیادی دارند می‌تواند ساختار کد را بسیار منظم‌تر کند.

در Laravel نیز می‌توان از این ایده برای سناریوهایی مانند:

  • بررسی شرایط خرید
  • قوانین تخفیف
  • سطح دسترسی
  • شرایط سفارش
  • وضعیت اشتراک
  • شرایط ارسال
  • قوانین حسابداری
  • فیلترهای پیچیده

استفاده کرد.

نکته مهم این است که Specification را زمانی وارد معماری کنیم که واقعاً مشکل تکرار، پیچیدگی و ترکیب Business Ruleها را حل کند، نه صرفاً برای اینکه از یک Design Pattern استفاده کرده باشیم.


# سوالات متداول Specification Pattern در PHP

Specification Pattern در PHP چیست؟

Specification Pattern روشی برای تبدیل قوانین کسب‌وکار به Objectهای مستقل و قابل استفاده مجدد است. این Objectها معمولاً با متدی مانند isSatisfiedBy() بررسی می‌کنند که یک Object شرایط مشخصی را دارد یا خیر.

متد isSatisfiedBy در Specification چه کاری انجام می‌دهد؟

این متد بررسی می‌کند که Object موردنظر Specification را برآورده می‌کند یا خیر و معمولاً یک مقدار true یا false برمی‌گرداند.

Composite Specification چیست؟

Composite Specification به ما اجازه می‌دهد چند Specification را با یکدیگر ترکیب کنیم؛ برای مثال با منطق AND، OR و NOT.

آیا Specification Pattern در Laravel کاربرد دارد؟

بله. می‌توان از ایده Specification برای مدیریت قوانین کسب‌وکار مانند شرایط خرید، تخفیف، دسترسی کاربران، وضعیت سفارش و حتی طراحی منطق فیلتر Queryها استفاده کرد.

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

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

آیا برای هر شرط ساده باید Specification بسازیم؟

خیر. اگر شرط بسیار ساده و فقط در یک نقطه استفاده می‌شود، ساخت Specification احتمالاً پیچیدگی غیرضروری ایجاد می‌کند.

آیا Specification فقط برای PHP است؟

خیر. Specification یک مفهوم طراحی عمومی برای مدل‌سازی Business Ruleهاست، اما در این مقاله نحوه پیاده‌سازی آن را با PHP بررسی کردیم.

مزیت اصلی Specification Pattern چیست؟

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

نظرات (0)

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

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

مقالات مرتبط

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

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

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

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

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

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

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

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

۲۲ دقیقه