وقتی یک پروژه 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 زیادی دارند، میتواند کاربردی باشد.





