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

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

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

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

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

در پروژه‌های PHP گاهی چند کلاس مختلف داریم که یک فرآیند تقریباً یکسان را انجام می‌دهند؛ اما بعضی مراحل آن‌ها با یکدیگر تفاوت دارد.

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

  • گزارش فروش روزانه
  • گزارش فروش ماهانه
  • گزارش فروش سالانه

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

  1. دریافت اطلاعات
  2. پردازش داده‌ها
  3. محاسبه آمار
  4. ساخت خروجی
  5. ذخیره یا نمایش گزارش

اگر این مراحل را در هر کلاس به‌صورت جداگانه بنویسیم، مقدار زیادی کد تکراری ایجاد می‌شود. همچنین اگر ترتیب مراحل تغییر کند، باید چند کلاس را به‌صورت هم‌زمان اصلاح کنیم.

Template Method Pattern برای حل این مشکل استفاده می‌شود. در این الگو، اسکلت کلی الگوریتم در یک کلاس پایه تعریف می‌شود و کلاس‌های فرزند فقط مراحل متفاوت را پیاده‌سازی یا تغییر می‌دهند. <Cite refs={["turn0search0","turn0search2","turn0search4"]} />

Template Method Pattern چیست؟

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

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

به زبان ساده:

> کلاس پایه مشخص می‌کند «چه مراحلی و با چه ترتیبی» اجرا شوند؛ کلاس‌های فرزند مشخص می‌کنند «هر مرحله دقیقاً چگونه» اجرا شود.

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

  • متدهای عادی با پیاده‌سازی مشترک
  • متدهای Abstract که فرزند باید آن‌ها را پیاده‌سازی کند
  • متدهای Hook که پیاده‌سازی پیش‌فرض دارند و در صورت نیاز قابل تغییر هستند

در مستندات طراحی این الگو، Template Method به‌عنوان روشی برای حفظ ساختار الگوریتم، حذف کدهای تکراری و امکان توسعه بخش‌های خاص از طریق Inheritance معرفی شده است. <Cite refs={["turn0search2","turn0search4"]} />

مشکل کد تکراری بدون Template Method

فرض کنید دو کلاس برای ارسال پیام داریم:

  • ارسال پیامک
  • ارسال ایمیل

هر دو فرآیند مراحل مشابهی دارند:

  1. اعتبارسنجی پیام
  2. آماده‌سازی محتوا
  3. ارسال پیام
  4. ثبت نتیجه

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


<?php



declare(strict_types=1);



final class SmsSender

{

    public function send(string $message): void

    {

        $this->validate($message);

        $this->prepare($message);

        $this->sendSms($message);

        $this->logResult();

    }



    private function validate(string $message): void

    {

        // اعتبارسنجی پیامک

    }



    private function prepare(string $message): void

    {

        // آماده‌سازی پیامک

    }



    private function sendSms(string $message): void

    {

        // ارسال پیامک

    }



    private function logResult(): void

    {

        // ثبت نتیجه

    }

}

کلاس ارسال ایمیل نیز تقریباً همین ساختار را تکرار می‌کند:


<?php



declare(strict_types=1);



final class EmailSender

{

    public function send(string $message): void

    {

        $this->validate($message);

        $this->prepare($message);

        $this->sendEmail($message);

        $this->logResult();

    }



    private function validate(string $message): void

    {

        // اعتبارسنجی ایمیل

    }



    private function prepare(string $message): void

    {

        // آماده‌سازی ایمیل

    }



    private function sendEmail(string $message): void

    {

        // ارسال ایمیل

    }



    private function logResult(): void

    {

        // ثبت نتیجه

    }

}

در این روش، ترتیب اجرای مراحل در دو کلاس تکرار شده است. اگر بخواهیم مرحله جدیدی مانند checkRateLimit اضافه کنیم، باید آن را در چند کلاس قرار دهیم.

Template Method این ساختار مشترک را به کلاس پایه منتقل می‌کند.

اجزای اصلی Template Method Pattern

۱. Abstract Class

کلاس پایه‌ای است که اسکلت الگوریتم را تعریف می‌کند.

این کلاس معمولاً یک متد عمومی دارد که مراحل الگوریتم را به‌ترتیب اجرا می‌کند.

۲. Template Method

متدی است که ترتیب اجرای مراحل را مشخص می‌کند.

برای جلوگیری از تغییر ترتیب مراحل توسط کلاس‌های فرزند، معمولاً این متد با کلمه کلیدی final تعریف می‌شود.

۳. Primitive Operations

متدهایی هستند که هر کلاس فرزند باید آن‌ها را پیاده‌سازی کند.

این متدها معمولاً به‌صورت abstract تعریف می‌شوند.

۴. Hook Methods

متدهایی هستند که در کلاس پایه پیاده‌سازی پیش‌فرض دارند؛ اما کلاس فرزند در صورت نیاز می‌تواند آن‌ها را Override کند.

۵. Concrete Classes

کلاس‌های فرزندی هستند که مراحل اختصاصی الگوریتم را پیاده‌سازی می‌کنند.

ساختار کلی Template Method

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


AbstractClass

    |

    +-- templateMethod()

    |       |

    |       +-- baseOperation()

    |       +-- requiredOperation()

    |       +-- hook()

    |

    +-- ConcreteClassA

    |

    +-- ConcreteClassB

کلاس‌های فرزند از AbstractClass ارث‌بری می‌کنند. آن‌ها می‌توانند مراحل Abstract یا Hook را تغییر دهند؛ اما نباید ترتیب اجرای الگوریتم اصلی را تغییر دهند.

پیاده‌سازی Template Method در PHP

برای مثال، یک سیستم تولید گزارش ایجاد می‌کنیم.

فرآیند تولید هر گزارش شامل مراحل زیر است:

  1. دریافت داده‌ها
  2. پردازش داده‌ها
  3. ساخت گزارش
  4. نمایش گزارش

مرحله دریافت داده‌ها و ساخت گزارش در انواع مختلف گزارش متفاوت است؛ اما ترتیب کلی باید ثابت بماند.

مرحله اول: ساخت کلاس Abstract


<?php



declare(strict_types=1);



abstract class ReportGenerator

{

    final public function generate(): void

    {

        $data = $this->fetchData();



        $processedData = $this->processData($data);



        $report = $this->buildReport($processedData);



        $this->displayReport($report);

    }



    abstract protected function fetchData(): array;



    abstract protected function processData(array $data): array;



    abstract protected function buildReport(array $data): string;



    protected function displayReport(string $report): void

    {

        echo $report . PHP_EOL;

    }

}

در این کلاس، متد generate همان Template Method است.

این متد مراحل را با ترتیب مشخص اجرا می‌کند:


fetchData

processData

buildReport

displayReport

کلمه کلیدی final باعث می‌شود کلاس‌های فرزند نتوانند متد generate را Override کنند.

در نتیجه، ترتیب کلی الگوریتم همیشه ثابت باقی می‌ماند.

مرحله دوم: ساخت گزارش فروش

اکنون یک کلاس فرزند برای گزارش فروش ایجاد می‌کنیم:


<?php



declare(strict_types=1);



final class SalesReportGenerator extends ReportGenerator

{

    protected function fetchData(): array

    {

        return [

            ['product' => 'Laptop', 'amount' => 20000000],

            ['product' => 'Mouse', 'amount' => 1000000],

            ['product' => 'Keyboard', 'amount' => 3000000],

        ];

    }



    protected function processData(array $data): array

    {

        $total = 0;



        foreach ($data as $item) {

            $total += $item['amount'];

        }



        return [

            'items' => $data,

            'total' => $total,

        ];

    }



    protected function buildReport(array $data): string

    {

        return sprintf(

            'Total sales: %d',

            $data['total'],

        );

    }

}

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

برای اجرای آن:


<?php



$report = new SalesReportGenerator();



$report->generate();

خروجی:


Total sales: 24000000

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

ساخت گزارش موجودی

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


<?php



declare(strict_types=1);



final class InventoryReportGenerator extends ReportGenerator

{

    protected function fetchData(): array

    {

        return [

            ['product' => 'Laptop', 'stock' => 5],

            ['product' => 'Mouse', 'stock' => 20],

            ['product' => 'Keyboard', 'stock' => 10],

        ];

    }



    protected function processData(array $data): array

    {

        $totalStock = 0;



        foreach ($data as $item) {

            $totalStock += $item['stock'];

        }



        return [

            'items' => $data,

            'totalStock' => $totalStock,

        ];

    }



    protected function buildReport(array $data): string

    {

        return sprintf(

            'Total stock: %d',

            $data['totalStock'],

        );

    }

}

استفاده:


<?php



$report = new InventoryReportGenerator();



$report->generate();

خروجی:


Total stock: 35

هر دو کلاس از یک الگوریتم مشترک استفاده می‌کنند، اما نحوه دریافت، پردازش و ساخت خروجی در آن‌ها متفاوت است.

استفاده از Hook Method

Hook متدی است که در کلاس پایه پیاده‌سازی پیش‌فرض دارد. کلاس‌های فرزند مجبور نیستند آن را تغییر دهند؛ اما در صورت نیاز می‌توانند آن را Override کنند.

برای مثال، می‌خواهیم قبل از نمایش گزارش، یک عنوان چاپ شود.


<?php



declare(strict_types=1);



abstract class ReportGeneratorWithHook

{

    final public function generate(): void

    {

        $data = $this->fetchData();



        $processedData = $this->processData($data);



        $report = $this->buildReport($processedData);



        if ($this->shouldDisplayHeader()) {

            echo "===== REPORT =====" . PHP_EOL;

        }



        echo $report . PHP_EOL;

    }



    abstract protected function fetchData(): array;



    abstract protected function processData(array $data): array;



    abstract protected function buildReport(array $data): string;



    protected function shouldDisplayHeader(): bool

    {

        return true;

    }

}

کلاس فرزند می‌تواند رفتار Hook را تغییر دهد:


<?php



declare(strict_types=1);



final class SimpleReport extends ReportGeneratorWithHook

{

    protected function fetchData(): array

    {

        return [10, 20, 30];

    }



    protected function processData(array $data): array

    {

        return [

            'total' => array_sum($data),

        ];

    }



    protected function buildReport(array $data): string

    {

        return 'Total: ' . $data['total'];

    }



    protected function shouldDisplayHeader(): bool

    {

        return false;

    }

}

در این مثال، shouldDisplayHeader یک Hook است. اگر کلاس فرزند آن را Override نکند، مقدار پیش‌فرض true استفاده می‌شود.

مثال کاربردی‌تر: ارسال پیام

اکنون یک مثال نزدیک‌تر به پروژه‌های واقعی ایجاد می‌کنیم. فرض کنید ارسال پیامک و ایمیل مراحل مشترکی دارند:

  1. اعتبارسنجی پیام
  2. آماده‌سازی محتوا
  3. ارسال
  4. ثبت گزارش

کلاس پایه


<?php



declare(strict_types=1);



abstract class MessageSender

{

    final public function send(string $message): void

    {

        $this->validate($message);



        $preparedMessage = $this->prepare($message);



        $this->deliver($preparedMessage);



        $this->log($preparedMessage);

    }



    protected function validate(string $message): void

    {

        if (trim($message) === '') {

            throw new InvalidArgumentException(

                'Message cannot be empty.',

            );

        }

    }



    abstract protected function prepare(string $message): string;



    abstract protected function deliver(string $message): void;



    protected function log(string $message): void

    {

        echo "Message logged: {$message}" . PHP_EOL;

    }

}

در این کلاس، validate و log رفتار مشترک دارند؛ اما prepare و deliver برای هر نوع پیام متفاوت هستند.

ارسال پیامک


<?php



declare(strict_types=1);



final class SmsMessageSender extends MessageSender

{

    protected function prepare(string $message): string

    {

        return '[SMS] ' . $message;

    }



    protected function deliver(string $message): void

    {

        echo "Sending SMS: {$message}" . PHP_EOL;

    }

}

ارسال ایمیل


<?php



declare(strict_types=1);



final class EmailMessageSender extends MessageSender

{

    protected function prepare(string $message): string

    {

        return '[EMAIL] ' . $message;

    }



    protected function deliver(string $message): void

    {

        echo "Sending email: {$message}" . PHP_EOL;

    }

}

اجرای کد


<?php



$smsSender = new SmsMessageSender();



$smsSender->send('Your order has been created.');



$emailSender = new EmailMessageSender();



$emailSender->send('Your order has been created.');

خروجی تقریبی:


Sending SMS: [SMS] Your order has been created.

Message logged: [SMS] Your order has been created.



Sending email: [EMAIL] Your order has been created.

Message logged: [EMAIL] Your order has been created.

در این مثال، Client فقط متد send را فراخوانی می‌کند و لازم نیست مراحل داخلی ارسال پیام را مدیریت کند.

اجرای کد با PHP

اگر کدها را در فایل زیر ذخیره کنید:


template-method.php

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


php template-method.php

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

Template Method در Laravel

در Laravel نیز می‌توان از Template Method برای ساخت Serviceهای مشابه استفاده کرد.

برای مثال، فرض کنید در پروژه خود چند نوع Importer دارید:

  • Import محصولات از CSV
  • Import محصولات از Excel
  • Import محصولات از API

فرآیند کلی Import در همه آن‌ها یکسان است:

  1. دریافت ورودی
  2. اعتبارسنجی
  3. تبدیل داده‌ها
  4. ذخیره در دیتابیس
  5. ثبت نتیجه

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


<?php



declare(strict_types=1);



abstract class ProductImporter

{

    final public function import(): int

    {

        $source = $this->loadSource();



        $validatedData = $this->validateData($source);



        $products = $this->transformData($validatedData);



        $count = $this->saveProducts($products);



        $this->afterImport($count);



        return $count;

    }



    abstract protected function loadSource(): array;



    protected function validateData(array $data): array

    {

        return array_values(

            array_filter(

                $data,

                static fn (array $item): bool =>

                    isset($item['name'], $item['price']),

            ),

        );

    }



    abstract protected function transformData(array $data): array;



    abstract protected function saveProducts(array $products): int;



    protected function afterImport(int $count): void

    {

        logger()->info(

            'Products imported',

            ['count' => $count],

        );

    }

}

کلاس‌های فرزند می‌توانند منبع داده و نحوه ذخیره را تغییر دهند، اما فرآیند اصلی Import ثابت باقی می‌ماند.

برای مثال، Import از API:


<?php



declare(strict_types=1);



final class ApiProductImporter extends ProductImporter

{

    protected function loadSource(): array

    {

        return [

            [

                'name' => 'Laptop',

                'price' => 20000000,

            ],

        ];

    }



    protected function transformData(array $data): array

    {

        return array_map(

            static fn (array $item): array => [

                'name' => $item['name'],

                'price' => (int) $item['price'],

            ],

            $data,

        );

    }



    protected function saveProducts(array $products): int

    {

        foreach ($products as $product) {

            // Product::create($product);

        }



        return count($products);

    }

}

در یک پروژه Laravel واقعی، می‌توان منطق ذخیره‌سازی را به Repository یا Service جداگانه منتقل کرد تا کلاس Importer بیش از حد بزرگ نشود.

آیا Template Method با Laravel Service Container سازگار است؟

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

برای مثال، اگر چند Importer داشته باشید، می‌توانید بر اساس نوع ورودی، کلاس مناسب را انتخاب کنید:


<?php



$importer = match ($sourceType) {

    'api' => app(ApiProductImporter::class),

    'csv' => app(CsvProductImporter::class),

    default => throw new InvalidArgumentException(

        'Unsupported source type.',

    ),

};



$count = $importer->import();

در این ساختار، Client فقط با متد مشترک import کار می‌کند و جزئیات کلاس فرزند را مدیریت نمی‌کند.

تفاوت Template Method و Strategy Pattern

Template Method و Strategy هر دو برای مدیریت الگوریتم‌های متفاوت استفاده می‌شوند؛ اما روش طراحی آن‌ها متفاوت است.

Template Method

  • بر پایه Inheritance است.
  • اسکلت الگوریتم در کلاس پایه قرار دارد.
  • کلاس فرزند مراحل خاص را Override می‌کند.
  • ساختار الگوریتم در سطح کلاس مشخص می‌شود.

Strategy

  • بر پایه Composition است.
  • هر الگوریتم در یک کلاس جدا قرار می‌گیرد.
  • رفتار می‌تواند در زمان اجرا تغییر کند.
  • معمولاً به Interface و Dependency Injection متکی است.
Template MethodStrategy
استفاده از ارث‌بریاستفاده از ترکیب Objectها
الگوریتم در کلاس پایه تعریف می‌شودالگوریتم در کلاس مستقل قرار دارد
تغییر رفتار با ساخت کلاس فرزندتغییر رفتار با تزریق Strategy
مناسب برای فرآیندهای ثابتمناسب برای تعویض رفتار در زمان اجرا

به‌صورت کلی، اگر ترتیب مراحل باید ثابت بماند و فقط بعضی مراحل تغییر کنند، Template Method انتخاب مناسبی است. اگر لازم است الگوریتم در زمان اجرا قابل تعویض باشد، Strategy معمولاً انعطاف بیشتری دارد. <Cite refs={["turn0search2","turn0search6"]} />

تفاوت Template Method و Factory Method

Factory Method می‌تواند یکی از مراحل داخل Template Method باشد.

برای مثال، Template Method ممکن است فرآیند ساخت گزارش را مشخص کند و در یکی از مراحل، Factory Method را برای ساخت نوع خاصی از Object فراخوانی کند.

بنابراین این دو الگو رقیب مستقیم یکدیگر نیستند و می‌توانند با هم استفاده شوند. <Cite ref="turn0search2" />

مزایای Template Method Pattern

حذف کدهای تکراری

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

حفظ ترتیب الگوریتم

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

توسعه‌پذیری

برای افزودن نوع جدید، معمولاً کافی است یک کلاس فرزند ایجاد کنید.

استفاده از Polymorphism

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

تمرکز مسئولیت‌ها

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

مناسب برای Frameworkها

این الگو در ساختارهای Frameworkمحور مفید است؛ زیرا Framework ترتیب کلی فرآیند را مشخص می‌کند و توسعه‌دهنده فقط نقاط قابل توسعه را پیاده‌سازی می‌کند. <Cite refs={["turn0search0","turn0search4"]} />

معایب Template Method Pattern

وابستگی به Inheritance

این الگو به ارث‌بری وابسته است. اگر ساختار ارث‌بری پیچیده شود، نگهداری کد دشوار خواهد شد.

محدودیت در تغییر الگوریتم

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

احتمال ایجاد کلاس پایه بزرگ

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

احتمال نقض Liskov Substitution Principle

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

سخت‌تر شدن تست

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

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

این الگو زمانی مناسب است که:

  • چند کلاس الگوریتمی با ساختار مشابه دارند.
  • ترتیب مراحل در همه کلاس‌ها ثابت است.
  • فقط بعضی مراحل بین کلاس‌ها تفاوت دارند.
  • کدهای تکراری زیادی در کلاس‌های مختلف وجود دارد.
  • می‌خواهید Client با یک API مشترک کار کند.
  • می‌خواهید نقاط قابل توسعه مشخصی برای کلاس‌های فرزند ایجاد کنید.

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

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

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

اگر الگوریتم‌ها ساختار کاملاً متفاوتی دارند، Template Method انتخاب مناسبی نیست.

همچنین در شرایط زیر بهتر است Strategy یا Composition را بررسی کنید:

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

در چنین شرایطی، استفاده از Interface و چند Service مستقل ممکن است طراحی انعطاف‌پذیرتری ایجاد کند.

نکات مهم در طراحی Template Method

Template Method را معمولاً final کنید

اگر ترتیب مراحل باید ثابت باشد، تعریف متد اصلی با final از Overrideشدن آن در کلاس‌های فرزند جلوگیری می‌کند.

مراحل را کوچک نگه دارید

بهتر است هر مرحله یک مسئولیت مشخص داشته باشد. متدهای بسیار بزرگ، مزیت Template Method را کاهش می‌دهند.

از Hook بیش از حد استفاده نکنید

Hookهای زیاد ممکن است فهم الگوریتم را دشوار کنند. فقط نقاط توسعه‌ای را به Hook تبدیل کنید که واقعاً به تغییر نیاز دارند.

کلاس پایه را بیش از حد عمومی نکنید

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

قرارداد متدها را واضح نگه دارید

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

منطق کسب‌وکار را از مراحل فنی جدا کنید

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

تست Template Method با PHPUnit

برای تست این الگو باید بررسی کنیم:

  • مراحل با ترتیب صحیح اجرا شوند.
  • کلاس فرزند بتواند مراحل Abstract را پیاده‌سازی کند.
  • Hook پیش‌فرض درست کار کند.
  • Hook قابل Override باشد.
  • Template Method توسط کلاس فرزند تغییر نکند.

یک نمونه ساده:


<?php



declare(strict_types=1);



use PHPUnit\Framework\TestCase;



final class ReportGeneratorTest extends TestCase

{

    public function testSalesReportCanBeGenerated(): void

    {

        $report = new SalesReportGenerator();



        ob_start();



        $report->generate();



        $output = ob_get_clean();



        self::assertSame(

            "Total sales: 24000000" . PHP_EOL,

            $output,

        );

    }

}

برای تست دقیق‌تر ترتیب مراحل، می‌توان یک کلاس آزمایشی ساخت که هر مرحله را در یک آرایه ثبت کند:


<?php



declare(strict_types=1);



final class TestReportGenerator extends ReportGenerator

{

    public array $steps = [];



    protected function fetchData(): array

    {

        $this->steps[] = 'fetch';



        return [];

    }



    protected function processData(array $data): array

    {

        $this->steps[] = 'process';



        return [];

    }



    protected function buildReport(array $data): string

    {

        $this->steps[] = 'build';



        return 'Done';

    }



    protected function displayReport(string $report): void

    {

        $this->steps[] = 'display';

    }

}

سپس می‌توان ترتیب اجرای مراحل را بررسی کرد:


<?php



declare(strict_types=1);



use PHPUnit\Framework\TestCase;



final class TestReportGeneratorTest extends TestCase

{

    public function testStepsRunInExpectedOrder(): void

    {

        $report = new TestReportGenerator();



        $report->generate();



        self::assertSame(

            ['fetch', 'process', 'build', 'display'],

            $report->steps,

        );

    }

}

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

Overrideکردن Template Method

اگر کلاس فرزند بتواند متد اصلی را Override کند، ممکن است ترتیب الگوریتم از بین برود. در چنین شرایطی بهتر است متد اصلی final باشد.

قرار دادن منطق زیاد در کلاس پایه

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

استفاده از ارث‌بری فقط برای حذف چند خط کد

اگر تفاوت کلاس‌ها بسیار کم است، شاید یک Service ساده یا تابع مشترک کافی باشد.

استفاده نادرست از Hook

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

بی‌توجهی به قرارداد کلاس فرزند

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

انتخاب Template Method برای رفتار قابل تعویض

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

جمع‌بندی

Template Method Pattern در PHP الگویی برای تعریف اسکلت ثابت یک الگوریتم در کلاس پایه است. کلاس‌های فرزند می‌توانند مراحل خاص الگوریتم را پیاده‌سازی یا Override کنند؛ اما ترتیب کلی فرآیند در کلاس پایه باقی می‌ماند.

اجزای اصلی این الگو عبارت‌اند از:

  • Abstract Class
  • Template Method
  • متدهای Abstract
  • متدهای مشترک
  • Hook Method
  • Concrete Classes

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

مهم‌ترین مزیت آن کاهش کد تکراری و حفظ ترتیب اجرای مراحل است. در مقابل، چون Template Method بر پایه Inheritance ساخته می‌شود، برای الگوریتم‌هایی که باید در زمان اجرا تغییر کنند یا ساختار کاملاً متفاوتی دارند، Strategy و Composition ممکن است انتخاب بهتری باشند.

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

Template Method Pattern در PHP چیست؟

Template Method Pattern الگویی رفتاری است که اسکلت یک الگوریتم را در کلاس پایه تعریف می‌کند و اجازه می‌دهد کلاس‌های فرزند فقط مراحل خاص آن را تغییر دهند.

Template Method چه تفاوتی با Strategy دارد؟

Template Method بر پایه ارث‌بری است، اما Strategy بر پایه Composition و تزریق یک الگوریتم مستقل کار می‌کند. Template Method ساختار الگوریتم را ثابت نگه می‌دارد؛ Strategy امکان تعویض رفتار را در زمان اجرا فراهم می‌کند.

چرا باید Template Method را با final تعریف کنیم؟

با استفاده از final، کلاس‌های فرزند نمی‌توانند ترتیب اجرای مراحل اصلی را تغییر دهند. این کار ساختار الگوریتم را ثابت نگه می‌دارد.

Hook Method در Template Method چیست؟

Hook متدی است که در کلاس پایه پیاده‌سازی پیش‌فرض دارد و کلاس فرزند در صورت نیاز می‌تواند آن را Override کند.

آیا Template Method در Laravel کاربرد دارد؟

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

آیا Template Method بخشی از Design Patternهای رفتاری است؟

بله. Template Method در دسته Behavioral Design Patternها قرار دارد. <Cite refs={["turn0search1","turn0search4"]} />

آیا Template Method همیشه بهترین انتخاب است؟

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

نظرات (0)

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

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

مقالات مرتبط

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

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

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

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

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

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

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

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

۲۲ دقیقه