در پروژههای واقعی 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
فرض کنید سیستم فروشگاهی داریم.
قوانین خرید محصول:
- محصول فعال باشد.
- محصول موجود باشد.
- قیمت کمتر از ۲۰ میلیون باشد.
میتوانیم سه 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 چیست؟
مهمترین مزیت آن تبدیل قوانین کسبوکار به اجزای مستقل، قابل تست و قابل ترکیب است؛ در نتیجه میتوان قوانین پیچیده را بدون ایجاد شرطهای بزرگ و تکراری مدیریت کرد.





