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

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

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

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

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

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

راه ساده برای پیاده‌سازی چنین شرایطی، استفاده از if و switch است؛ اما با بزرگ‌تر شدن پروژه، تعداد شرط‌ها افزایش پیدا می‌کند و تغییر یا تست کد دشوارتر می‌شود.

اینجاست که Strategy Pattern در PHP کاربرد پیدا می‌کند.

الگوی Strategy یکی از الگوهای طراحی رفتاری یا Behavioral Design Patterns است. هدف آن جدا کردن الگوریتم‌ها از کدی است که از آن‌ها استفاده می‌کند تا بتوانیم الگوریتم مناسب را در زمان اجرا انتخاب یا جایگزین کنیم. در منابع آموزشی الگوهای طراحی نیز Strategy با مفاهیمی مانند Context، Strategy و Concrete Strategy معرفی می‌شود. <Cite refs={["turn0search1","turn0search0"]} />

Strategy Pattern چیست؟

Strategy Pattern الگویی است که مجموعه‌ای از الگوریتم‌ها یا رفتارهای مختلف را در کلاس‌های جداگانه قرار می‌دهد و به همه آن‌ها یک قرارداد مشترک می‌دهد.

در این الگو:

  • هر الگوریتم در یک کلاس مستقل قرار می‌گیرد.
  • همه الگوریتم‌ها یک Interface مشترک را پیاده‌سازی می‌کنند.
  • کلاس اصلی یا Context به‌جای شناخت جزئیات الگوریتم، فقط با Interface کار می‌کند.
  • استراتژی مورد استفاده می‌تواند در زمان ساخت شیء یا در زمان اجرای برنامه تغییر کند.

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

اجزای اصلی Strategy Pattern

Strategy Pattern معمولاً از سه بخش تشکیل می‌شود:

۱. Strategy Interface

این Interface قرارداد مشترک تمام الگوریتم‌ها را مشخص می‌کند.

۲. Concrete Strategy

هر کلاس Concrete Strategy یک پیاده‌سازی واقعی از الگوریتم است.

برای مثال:

  • تخفیف درصدی
  • تخفیف مبلغ ثابت
  • بدون تخفیف

هرکدام یک Concrete Strategy محسوب می‌شوند.

۳. Context

Context کلاسی است که از Strategy استفاده می‌کند. این کلاس نباید به جزئیات پیاده‌سازی استراتژی‌ها وابسته باشد و باید فقط از Interface مشترک استفاده کند.

مشکل استفاده از if و switch

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


<?php



function calculatePrice(

    float $price,

    string $discountType

): float {

    if ($discountType === 'percentage') {

        return $price * 0.90;

    }



    if ($discountType === 'fixed') {

        return max(0, $price - 100000);

    }



    if ($discountType === 'vip') {

        return $price * 0.80;

    }



    return $price;

}

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

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

  • تخفیف مخصوص کاربران جدید
  • تخفیف جشنواره
  • تخفیف بر اساس تعداد خرید
  • تخفیف بر اساس کد تخفیف
  • تخفیف مخصوص محصولات خاص
  • تخفیف ترکیبی

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

پیاده‌سازی Strategy Pattern در PHP

برای شروع، یک Interface به نام DiscountStrategy ایجاد می‌کنیم:


<?php



declare(strict_types=1);



interface DiscountStrategy

{

    public function calculate(float $price): float;

}

این Interface می‌گوید هر استراتژی تخفیف باید متدی به نام calculate داشته باشد که قیمت نهایی را برگرداند.

استراتژی بدون تخفیف


<?php



declare(strict_types=1);



final class NoDiscountStrategy implements DiscountStrategy

{

    public function calculate(float $price): float

    {

        return $price;

    }

}

این استراتژی قیمت را بدون تغییر برمی‌گرداند.

استراتژی تخفیف درصدی


<?php



declare(strict_types=1);



final class PercentageDiscountStrategy implements DiscountStrategy

{

    public function __construct(

        private readonly float $percentage

    ) {

    }



    public function calculate(float $price): float

    {

        $discount = $price * ($this->percentage / 100);



        return max(0, $price - $discount);

    }

}

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

برای مثال، اگر قیمت محصول ۱ میلیون تومان و درصد تخفیف ۱۰ باشد، قیمت نهایی ۹۰۰ هزار تومان خواهد بود.

استراتژی تخفیف مبلغ ثابت


<?php



declare(strict_types=1);



final class FixedDiscountStrategy implements DiscountStrategy

{

    public function __construct(

        private readonly float $amount

    ) {

    }



    public function calculate(float $price): float

    {

        return max(0, $price - $this->amount);

    }

}

در این استراتژی، مقدار مشخصی از قیمت محصول کم می‌شود.

استفاده از max(0, ...) باعث می‌شود قیمت نهایی منفی نشود.

ساخت Context

حالا باید کلاسی ایجاد کنیم که از استراتژی تخفیف استفاده کند. این کلاس را PriceCalculator می‌نامیم:


<?php



declare(strict_types=1);



final class PriceCalculator

{

    public function __construct(

        private DiscountStrategy $strategy

    ) {

    }



    public function calculate(float $price): float

    {

        return $this->strategy->calculate($price);

    }



    public function changeStrategy(

        DiscountStrategy $strategy

    ): void {

        $this->strategy = $strategy;

    }

}

کلاس PriceCalculator همان Context است.

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

استفاده از Strategyهای مختلف


<?php



$calculator = new PriceCalculator(

    new PercentageDiscountStrategy(10)

);



echo $calculator->calculate(1000000);

// 900000

در این مثال، قیمت یک میلیون تومان است و تخفیف ۱۰ درصدی اعمال می‌شود.

حالا می‌توانیم بدون تغییر کد PriceCalculator، استراتژی را عوض کنیم:


<?php



$calculator->changeStrategy(

    new FixedDiscountStrategy(150000)

);



echo $calculator->calculate(1000000);

// 850000

در اینجا همان Context با یک الگوریتم متفاوت کار می‌کند.

این قابلیت یکی از مهم‌ترین مزیت‌های Strategy Pattern است: تغییر رفتار بدون تغییر کد اصلی استفاده‌کننده.

انتخاب استراتژی در زمان اجرا

گاهی اوقات نوع استراتژی از اطلاعات کاربر یا درخواست HTTP مشخص می‌شود.

برای مثال:


<?php



$discountType = $_GET['discount'] ?? 'none';



$strategy = match ($discountType) {

    'percentage' => new PercentageDiscountStrategy(10),

    'fixed' => new FixedDiscountStrategy(150000),

    default => new NoDiscountStrategy(),

};



$calculator = new PriceCalculator($strategy);



echo $calculator->calculate(1000000);

در این مثال، الگوریتم بر اساس مقدار ورودی انتخاب می‌شود.

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

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

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

  • ارسال با پست
  • ارسال با پیک
  • ارسال رایگان
  • ارسال سریع

به‌جای قرار دادن تمام محاسبات در یک کلاس، برای هر روش ارسال یک Strategy ایجاد می‌کنیم.

Interface مربوط به ارسال


<?php



declare(strict_types=1);



interface ShippingStrategy

{

    public function calculateCost(float $weight): float;

}

ارسال با پست


<?php



declare(strict_types=1);



final class PostShippingStrategy implements ShippingStrategy

{

    public function calculateCost(float $weight): float

    {

        return 80000 + ($weight * 10000);

    }

}

ارسال با پیک


<?php



declare(strict_types=1);



final class CourierShippingStrategy implements ShippingStrategy

{

    public function calculateCost(float $weight): float

    {

        return 120000 + ($weight * 15000);

    }

}

ارسال رایگان


<?php



declare(strict_types=1);



final class FreeShippingStrategy implements ShippingStrategy

{

    public function calculateCost(float $weight): float

    {

        return 0;

    }

}

Context مربوط به ارسال


<?php



declare(strict_types=1);



final class ShippingCalculator

{

    public function __construct(

        private ShippingStrategy $strategy

    ) {

    }



    public function calculate(float $weight): float

    {

        return $this->strategy->calculateCost($weight);

    }

}

استفاده از کلاس ارسال


<?php



$shipping = new ShippingCalculator(

    new PostShippingStrategy()

);



echo $shipping->calculate(2);

اگر بعداً بخواهیم ارسال با پیک را فعال کنیم، فقط Strategy را تغییر می‌دهیم:


<?php



$shipping = new ShippingCalculator(

    new CourierShippingStrategy()

);

کلاس ShippingCalculator نیازی به تغییر ندارد.

استفاده از Strategy با Dependency Injection

یکی از بهترین روش‌ها برای استفاده از Strategy Pattern، ترکیب آن با Dependency Injection است.

در این روش، Strategy از بیرون به Context تزریق می‌شود:


<?php



declare(strict_types=1);



final class ReportService

{

    public function __construct(

        private ReportExportStrategy $exportStrategy

    ) {

    }



    public function export(array $data): string

    {

        return $this->exportStrategy->export($data);

    }

}

حالا می‌توانیم خروجی گزارش را با فرمت‌های مختلف تولید کنیم:


<?php



interface ReportExportStrategy

{

    public function export(array $data): string;

}

خروجی CSV


<?php



final class CsvExportStrategy implements ReportExportStrategy

{

    public function export(array $data): string

    {

        $lines = [];



        foreach ($data as $row) {

            $lines[] = implode(',', $row);

        }



        return implode(PHP_EOL, $lines);

    }

}

خروجی JSON


<?php



final class JsonExportStrategy implements ReportExportStrategy

{

    public function export(array $data): string

    {

        return json_encode(

            $data,

            JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR

        );

    }

}

استفاده از ReportService


<?php



$reportService = new ReportService(

    new JsonExportStrategy()

);



echo $reportService->export([

    ['id' => 1, 'name' => 'Product A'],

    ['id' => 2, 'name' => 'Product B'],

]);

در این ساختار، سرویس گزارش به JSON یا CSV وابسته نیست؛ بلکه به قرارداد ReportExportStrategy وابسته است.

پیاده‌سازی Strategy در Laravel

در Laravel می‌توان Strategy Pattern را با Service Container، Interface و Dependency Injection پیاده‌سازی کرد.

فرض کنید یک سیستم پرداخت داریم که باید از چند درگاه پشتیبانی کند:

  • درگاه داخلی
  • درگاه بین‌المللی
  • پرداخت آزمایشی
  • پرداخت کیف پول

ساخت Interface


<?php



namespace App\Contracts;



interface PaymentStrategy

{

    public function pay(

        int $amount,

        array $data = []

    ): array;

}

استراتژی پرداخت داخلی


<?php



namespace App\Services\Payments;



use App\Contracts\PaymentStrategy;



final class LocalPaymentStrategy implements PaymentStrategy

{

    public function pay(

        int $amount,

        array $data = []

    ): array {

        return [

            'status' => 'success',

            'gateway' => 'local',

            'amount' => $amount,

            'reference' => 'LOCAL-' . uniqid(),

        ];

    }

}

استراتژی پرداخت آزمایشی


<?php



namespace App\Services\Payments;



use App\Contracts\PaymentStrategy;



final class FakePaymentStrategy implements PaymentStrategy

{

    public function pay(

        int $amount,

        array $data = []

    ): array {

        return [

            'status' => 'success',

            'gateway' => 'fake',

            'amount' => $amount,

            'reference' => 'FAKE-' . uniqid(),

        ];

    }

}

در پروژه واقعی باید به‌جای uniqid() از سازوکار مناسب تولید شناسه تراکنش استفاده شود. این مثال فقط برای نمایش ساختار Strategy است.

ساخت PaymentService


<?php



namespace App\Services;



use App\Contracts\PaymentStrategy;



final class PaymentService

{

    public function __construct(

        private PaymentStrategy $strategy

    ) {

    }



    public function pay(

        int $amount,

        array $data = []

    ): array {

        return $this->strategy->pay($amount, $data);

    }

}

انتخاب Strategy در Controller


<?php



namespace App\Http\Controllers;



use App\Services\PaymentService;

use App\Services\Payments\FakePaymentStrategy;

use App\Services\Payments\LocalPaymentStrategy;

use Illuminate\Http\Request;

use InvalidArgumentException;



final class PaymentController

{

    public function pay(Request $request): array

    {

        $gateway = $request->string('gateway')->toString();



        $strategy = match ($gateway) {

            'local' => new LocalPaymentStrategy(),

            'fake' => new FakePaymentStrategy(),

            default => throw new InvalidArgumentException(

                'درگاه پرداخت معتبر نیست.'

            ),

        };



        $paymentService = new PaymentService($strategy);



        return $paymentService->pay(

            amount: (int) $request->input('amount'),

            data: $request->all()

        );

    }

}

در پروژه‌های بزرگ‌تر، بهتر است انتخاب Strategy در یک Factory یا Resolver مستقل انجام شود تا Controller بیش از حد درگیر ساخت کلاس‌ها نشود.

ساخت Strategy Resolver

برای جدا کردن منطق انتخاب، می‌توانیم Resolver بسازیم:


<?php



namespace App\Services\Payments;



use App\Contracts\PaymentStrategy;

use InvalidArgumentException;



final class PaymentStrategyResolver

{

    public function resolve(string $gateway): PaymentStrategy

    {

        return match ($gateway) {

            'local' => app(LocalPaymentStrategy::class),

            'fake' => app(FakePaymentStrategy::class),

            default => throw new InvalidArgumentException(

                'درگاه پرداخت پشتیبانی نمی‌شود.'

            ),

        };

    }

}

سپس در Controller:


<?php



public function pay(

    Request $request,

    PaymentStrategyResolver $resolver

): array {

    $strategy = $resolver->resolve(

        $request->string('gateway')->toString()

    );



    $paymentService = new PaymentService($strategy);



    return $paymentService->pay(

        amount: (int) $request->input('amount'),

        data: $request->all()

    );

}

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

  • Controller درخواست را دریافت می‌کند.
  • Resolver استراتژی مناسب را انتخاب می‌کند.
  • PaymentService عملیات پرداخت را اجرا می‌کند.
  • Concrete Strategy جزئیات درگاه را پیاده‌سازی می‌کند.

تفاوت Strategy Pattern با if و switch

استفاده از if یا switch همیشه اشتباه نیست. برای تعداد کمی شرط ساده، همان روش می‌تواند خواناتر و مناسب‌تر باشد.

Strategy زمانی ارزشمند می‌شود که:

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

مقایسه کلی:

روش شرطیStrategy Pattern
الگوریتم‌ها در یک تابع جمع می‌شوندهر الگوریتم کلاس مستقل دارد
با افزایش حالت‌ها کد شلوغ می‌شوداضافه کردن Strategy جدید ساده‌تر است
تست بخش‌ها دشوارتر استهر Strategy جداگانه تست می‌شود
وابستگی به جزئیات بیشتر استContext به Interface وابسته است
تغییر رفتار ممکن است نیازمند تغییر تابع اصلی باشدرفتار از بیرون قابل تعویض است

تفاوت Strategy و State

Strategy و State شباهت زیادی دارند؛ زیرا هر دو می‌توانند رفتار را در کلاس‌های جداگانه قرار دهند.

اما هدف آن‌ها متفاوت است.

Strategy

در Strategy معمولاً برنامه‌نویس یا سیستم تصمیم می‌گیرد کدام الگوریتم استفاده شود.

برای مثال:

  • انتخاب روش پرداخت
  • انتخاب نوع تخفیف
  • انتخاب روش ارسال
  • انتخاب فرمت خروجی

State

در State رفتار شیء بر اساس وضعیت داخلی آن تغییر می‌کند.

برای مثال، یک سفارش ممکن است وضعیت‌های زیر را داشته باشد:

  • ایجادشده
  • پرداخت‌شده
  • ارسال‌شده
  • تحویل‌شده
  • لغوشده

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

به‌صورت خلاصه:

  • Strategy یعنی انتخاب الگوریتم.
  • State یعنی تغییر رفتار بر اساس وضعیت داخلی.

تفاوت Strategy و Factory

Factory مسئول ساخت یا ایجاد شیء است؛ اما Strategy مسئول پیاده‌سازی رفتار قابل تعویض است.

برای مثال:


<?php



$strategy = PaymentStrategyFactory::make('local');

در اینجا Factory استراتژی را می‌سازد یا انتخاب می‌کند، اما عملیات پرداخت توسط خود Strategy انجام می‌شود.

بنابراین می‌توان Factory و Strategy را با هم استفاده کرد:

  • Factory: کدام Strategy ساخته شود؟
  • Strategy: الگوریتم انتخاب‌شده چگونه اجرا شود؟

تفاوت Strategy و Template Method

در Template Method معمولاً یک کلاس پایه، ساختار کلی الگوریتم را مشخص می‌کند و کلاس‌های فرزند بخش‌های خاص را تغییر می‌دهند.

اما در Strategy، الگوریتم‌ها معمولاً در کلاس‌های مستقل و هم‌سطح قرار می‌گیرند و از طریق Interface قابل تعویض هستند.

Strategy بیشتر بر ترکیب اشیا و Dependency Injection تکیه دارد، در حالی که Template Method بیشتر بر ارث‌بری تکیه می‌کند.

مزایای Strategy Pattern در PHP

۱. کاهش شرط‌های پیچیده

با جدا کردن الگوریتم‌ها، تعداد شرط‌های داخل سرویس‌های اصلی کمتر می‌شود.

۲. رعایت اصل Open/Closed

برای اضافه کردن یک الگوریتم جدید، معمولاً لازم نیست کد Context را تغییر دهیم. کافی است Strategy جدیدی ایجاد کنیم.

۳. تست‌پذیری بهتر

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

۴. استفاده مجدد از الگوریتم‌ها

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

۵. تعویض رفتار در زمان اجرا

می‌توان بر اساس تنظیمات، نقش کاربر، نوع سفارش یا محیط اجرا، Strategy مناسب را انتخاب کرد.

۶. کاهش وابستگی

Context به Interface وابسته است، نه به کلاس‌های پیاده‌سازی مشخص.

معایب Strategy Pattern

Strategy همیشه بهترین انتخاب نیست و ممکن است در پروژه‌های کوچک باعث پیچیدگی اضافه شود.

معایب احتمالی عبارت‌اند از:

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

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

اشتباهات رایج در استفاده از Strategy

ایجاد Strategy برای هر شرط کوچک

اگر یک شرط ساده و ثابت است، لازم نیست حتماً برای آن کلاس جداگانه بسازید.

وابسته کردن Context به کلاس‌های Concrete

کد زیر طراحی مناسبی ندارد:


<?php



final class PriceCalculator

{

    public function calculate(

        float $price,

        PercentageDiscountStrategy $strategy

    ): float {

        return $strategy->calculate($price);

    }

}

در این حالت Context مستقیماً به یک پیاده‌سازی خاص وابسته است.

طراحی بهتر:


<?php



final class PriceCalculator

{

    public function calculate(

        float $price,

        DiscountStrategy $strategy

    ): float {

        return $strategy->calculate($price);

    }

}

قرار دادن منطق انتخاب داخل همه Strategyها

هر Strategy باید روی اجرای الگوریتم خودش تمرکز کند. انتخاب Strategy بهتر است در Resolver، Factory یا لایه مناسب دیگری انجام شود.

استفاده از Interface بیش از حد عمومی

Interface باید قرارداد مشخص و قابل فهمی داشته باشد. برای مثال، متد زیر بیش از حد عمومی است:


public function execute(mixed $data): mixed;

اگر مشخص باشد Strategy دقیقاً چه کاری انجام می‌دهد، بهتر است امضای متد نیز واضح باشد:


public function calculate(float $price): float;

نادیده گرفتن اعتبارسنجی ورودی

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

تست Strategy با PHPUnit

یکی از مزیت‌های مهم Strategy، تست مستقل هر الگوریتم است.

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


<?php



use PHPUnit\Framework\TestCase;



final class PercentageDiscountStrategyTest extends TestCase

{

    public function test_it_calculates_percentage_discount(): void

    {

        $strategy = new PercentageDiscountStrategy(10);



        $result = $strategy->calculate(1000000);



        self::assertSame(900000.0, $result);

    }

}

نمونه تست برای تخفیف مبلغ ثابت:


<?php



use PHPUnit\Framework\TestCase;



final class FixedDiscountStrategyTest extends TestCase

{

    public function test_it_does_not_return_negative_price(): void

    {

        $strategy = new FixedDiscountStrategy(200000);



        $result = $strategy->calculate(100000);



        self::assertSame(0.0, $result);

    }

}

همچنین می‌توان Context را با یک Strategy ساختگی تست کرد:


<?php



final class TestDiscountStrategy implements DiscountStrategy

{

    public function calculate(float $price): float

    {

        return 123.0;

    }

}

سپس:


<?php



$calculator = new PriceCalculator(

    new TestDiscountStrategy()

);



assert($calculator->calculate(500000) === 123.0);

این روش باعث می‌شود تست Context به منطق واقعی تخفیف وابسته نباشد.

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

استفاده از Strategy انتخاب مناسبی است وقتی:

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

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

در شرایط زیر بهتر است ابتدا راهکار ساده‌تری را بررسی کنید:

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

هدف Design Patternها پیچیده کردن کد نیست؛ هدف آن‌ها حل مسئله‌های تکرارشونده با ساختاری قابل نگهداری است.

جمع‌بندی

Strategy Pattern در PHP راهکاری برای جدا کردن الگوریتم‌ها و رفتارهای مختلف از Context است. در این الگو، هر الگوریتم در یک کلاس مستقل قرار می‌گیرد و همه الگوریتم‌ها از یک Interface مشترک پیروی می‌کنند.

با استفاده از Strategy می‌توان:

  • الگوریتم‌ها را از هم جدا کرد.
  • رفتار برنامه را در زمان اجرا تغییر داد.
  • شرط‌های طولانی را کاهش داد.
  • تست‌نویسی را ساده‌تر کرد.
  • اصل Open/Closed را بهتر رعایت کرد.
  • در Laravel از Dependency Injection و Service Container استفاده کرد.

برای سیستم‌هایی مانند پرداخت، تخفیف، ارسال سفارش، خروجی گزارش، احراز هویت و انتخاب روش ذخیره‌سازی، Strategy Pattern می‌تواند ساختار کد را بسیار قابل نگهداری‌تر کند.

نظرات (0)

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

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

مقالات مرتبط

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

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

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

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

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

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

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

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

۲۲ دقیقه