توسعه‌دهندگانی که اپلیکیشن‌های Laravel می‌سازند، تشویق می‌شوند که از کلمه کلیدی new در کنترلرهای خود دست بکشند و به سراغ الگوی Factory Method بروند. با انتقال فرآیند ساخت اشیاء به فکتوری‌های اختصاصی، کدها برای گسترش، تست و نگهداری آسان‌تر می‌شوند؛ مزایایی که با بزرگ شدن پروژه‌ها و فراتر رفتن از چند Endpoint، اهمیت دوچندان می‌یابند.

چرا کلمه کلیدی new یک ریسک پنهان است

وقتی یک کنترلر شامل خطی مانند

$processor = new StripePaymentProcessor($config);

باشد، آن کنترلر اکنون به شدت به کلاس StripePaymentProcessor وابسته (tightly coupled) شده است. هر جایی که به یک پردازشگر پرداخت نیاز داشته باشد، آن خط را تکرار می‌کند و منطق ساخت (construction logic) را در سراسر کد پخش می‌کند. اگر بعداً پردازشگر به یک Logger، یک Cache Manager یا فرمت پیکربندی متفاوتی نیاز داشته باشد، تمام آن نقاط باید به‌روزرسانی شوند. نتیجه، کدی شکننده است که در برابر تغییر مقاومت می‌کند و Mock کردن آن در تست‌های واحد (unit tests) دشوار است.

ورود الگوی Factory Method

الگوی Factory Method جایگزین نمونه‌سازی مستقیم (direct instantiation) با متدی می‌شود که نمونه‌ای از یک اینترفیس مشخص را برمی‌گرداند. کنترلر به اینترفیس وابسته است، در حالی که یک کلاس فکتوری می‌داند کدام کلاسِ عینی (concrete class) را باید بسازد و وابستگی‌های آن را چگونه ترکیب کند. تمام منطق ساخت در یک مکان قرار دارد، بنابراین افزودن یک وابستگی جدید یا تعویض پیاده‌سازی‌ها، تنها فکتوری را تحت تأثیر قرار می‌دهد.

مزایای اصلی

  • اتصال سست (Loose coupling) – کنترلرها با انتزاع‌ها (abstractions) کار می‌کنند، نه با کلاس‌های عینی.
  • ساخت متمرکز – تغییر فرآیند ساخت تنها با ویرایش یک فایل امکان‌پذیر است.
  • قابلیت تست – فکتوری‌ها را می‌توان با Stub یا Mock جایگزین کرد که امکان تست‌های ایزوله کنترلر را فراهم می‌کند.
  • اصل باز/بسته (Open/Closed Principle) – ویژگی‌های جدید (مثلاً یک درگاه پرداخت جدید) را می‌توان بدون دستکاری در کدهای موجودِ کنترلر اضافه کرد.

یک آنالوژی در کافی‌شاپ

باریستایی را تصور کنید که باید برای هر سفارش، با استفاده از مجموعه‌ای طولانی از دستورات if-else دستی دانه‌ها را آسیاب کند، شیر را بخارده و آب را بریزد. اگر دستور تهیه لاته تغییر کند، هر باریستا باید مراحل را دوباره یاد بگیرد. یک دستگاه قهوه‌ساز که نوع سفارش را دریافت کرده و آماده‌سازی را به صورت داخلی انجام می‌دهد، این مشکل را حل می‌کند: باریستا فقط به دستگاه می‌گوید چه چیزی نیاز است و دستگاه تمام مراحل را کپسوله‌سازی (encapsulate) می‌کند. اکنون به‌روزرسانی دستور تهیه به معنای تنظیم دستگاه است، نه آموزش دوباره به تک‌تک باریستاها.

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

اجزای اصلی خودِ لاراول، این الگو را در عمل نشان می‌دهند:

  • Database – کلاس ConnectionFactory تصمیم می‌گیرد که یک اتصال MySQL بسازد یا PostgreSQL.
  • Queues – کلاس QueueManager درایورهایی برای Redis، SQS یا سایر بک‌اِندها ایجاد می‌کند.
  • Filesystem – کلاس FilesystemManager نمونه‌هایی از دیسک‌های local یا S3 تولید می‌کند.
  • Mail – کلاس MailManager درایورهای SMTP، Mailgun یا سایر درایورهای ایمیل را برمی‌گرداند.

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

چه زمانی یک فکتوری معرفی کنیم

از یک فکتوری استفاده کنید وقتی:

  • ساخت شیء شامل چندین مرحله پیکربندی یا سرویس‌های خارجی باشد.
  • چندین پیاده‌سازی قابل تعویض وجود داشته باشد (درگاه‌های پرداخت مختلف، ارائه‌دهندگان ذخیره‌سازی و غیره).
  • انتظار می‌رود لیست پیاده‌سازی‌های احتمالی رشد کند.

از فکتوری خودداری کنید وقتی:

  • ساخت شیء تنها یک فراخوانی new تغییرناپذیر و بدون تنظیمات اضافی باشد.
  • تنها از یک پیاده‌سازی استفاده خواهد شد و انتزاع اضافی، فقط باعث پیچیدگی بیهوده (overhead) می‌شود.

یک راهنمای سریع: بازنویسی (refactoring) یک کنترلر پرداخت

  1. تعریف یک اینترفیس – یک PaymentProcessorInterface با متد process(array $data).
  2. پیاده‌سازی کلاس‌های عینی – کلاس‌های StripePaymentProcessor و PayPalPaymentProcessor که هر کدام اینترفیس را پیاده‌سازی می‌کنند.
  3. ایجاد یک فکتوری – کلاس PaymentProcessorFactory با متد make(string $driver): PaymentProcessorInterface. در داخل آن، از یک switch یا یک map برای بازگرداندن کلاس مناسب استفاده کنید و هر سرویس مورد نیاز را از Service Container لاراول تزریق (inject) کنید.
  4. تزریق فکتوری – در سازنده (constructor) کنترلر، از PaymentProcessorFactory استفاده کنید (type-hint). لاراول آن را به‌طور خودکار Resolve می‌کند.
  5. استفاده از فکتوری – $processor = $this->processorFactory->make('stripe'); $processor->process($request->all());

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

معایب احتمالی و نحوه کاهش آن‌ها

انتقاد اصلی به فکتوری‌ها، اضافه شدن لایه‌های غیرمستقیم (indirection) است که می‌تواند برای اشیاء ساده، بیش از حد طولانی و پیچیده به نظر برسد. مهندسی بیش از حد (Over-engineering) یک سرویس ساده می‌تواند بدون سود واقعی، حجم کد را بیهوده افزایش دهد. نکته کلیدی، ارزیابی پیچیدگی ساخت است: اگر یک شیء صرفاً یک نگهدارنده داده (data holder) ساده و بدون وابستگی باشد، استفاده مستقیم از new می‌تواند قابل قبول باشد. همچنین، فکتوری‌ها ممکن است به مکانی برای انباشتن منطق‌های بی‌ربط تبدیل شوند. تمرکز فکتوری‌ها بر ساخت شیء و واگذاری تنظیمات (configuration) به سرویس‌دهنده‌های اختصاصی (service providers)، شفافیت کد را حفظ می‌کند.

موارد بعدی که باید مراقب آن‌ها باشید

  • Service container bindings – اگر رابط (interface) را در یک service provider به کلاس فکتوری متصل (bind) کنید، کانتینر Laravel می‌تواند فکتوری‌ها را به‌طور خودکار resolve کند.
  • Auto-discovery – برخی از پکیج‌ها فکتوری‌های خود را ارائه می‌دهند؛ مراقب تداخل نام‌ها (naming collisions) باشید.
  • Testing strategies – هنگام تست واحد (unit testing) کنترلرها، فکتوری را با یک mock جایگزین کنید که یک پردازشگر stub شده برمی‌گرداند؛ این کار تضمین می‌کند که تست‌ها سریع و ایزوله باقی بمانند.

خلاصه کلام

جایگزینی فراخوانی‌های پراکنده new با یک فکتوری در جای مناسب، فرآیند ساخت را متمرکز کرده، وابستگی‌ها (coupling) را کاهش می‌دهد و کد سفارشی شما را با فلسفه طراحی خود Laravel همسو می‌کند. برای تیم‌هایی که انتظار رشد دارند — مانند اضافه کردن درگاه‌های پرداخت جدید، زیرساخت‌های ذخیره‌سازی (storage back-ends) یا هرگونه کامپوننت سبک پلاگین — الگوی Factory Method سرمایه‌گذاری کم‌هزینه‌ای است که در قالب انعطاف‌پذیری و اطمینان، بازدهی خواهد داشت. خودِ آینده‌تان و هر کسی که کد را به ارث می‌برد، از شما سپاسگزار خواهد بود.