در برنامهنویسی شیگرا، یکی از کارهایی که دائماً انجام میدهیم، ایجاد Object از Classهای مختلف است.
در سادهترین حالت میتوانیم بنویسیم:
$user = new User();
یا:
$payment = new ZarinpalGateway();
این روش برای پروژههای ساده کاملاً مناسب است.
اما فرض کنید برنامه ما چند نوع مختلف از یک سرویس را داشته باشد.
مثلاً:
PaymentGateway
├── ZarinpalGateway
├── IdPayGateway
└── MellatGateway
اگر در قسمتهای مختلف برنامه مستقیماً از new استفاده کنیم، ممکن است منطق ایجاد Object در بخشهای مختلف پخش شود.
اینجاست که Factory Pattern میتواند مفید باشد.
Factory Pattern به ما کمک میکند منطق ایجاد Object را از بخشی که از آن Object استفاده میکند جدا کنیم.
# یک مثال ساده بدون Factory
فرض کنید یک سیستم ارسال Notification داریم:
interface Notification
{
public function send(string $message): void;
}
پیادهسازی SMS:
class SmsNotification implements Notification
{
public function send(string $message): void
{
echo "SMS: {$message}";
}
}
پیادهسازی Email:
class EmailNotification implements Notification
{
public function send(string $message): void
{
echo "Email: {$message}";
}
}
حالا در برنامه:
$type = 'sms';
if ($type === 'sms') {
$notification = new SmsNotification();
} elseif ($type === 'email') {
$notification = new EmailNotification();
}
$notification->send('سلام');
این کد کار میکند.
اما اگر چنین شرطهایی در چندین قسمت پروژه تکرار شوند، مدیریت کد سختتر میشود.
# Factory چه مشکلی را حل میکند؟
به جای اینکه هر قسمت برنامه مسئول ساخت Object باشد، میتوانیم یک Class مخصوص برای ایجاد Objectها داشته باشیم.
مثلاً:
Application
↓
NotificationFactory
↓
┌───────────────┐
│ │
SMS Email
در این حالت Application فقط میگوید:
$notification = NotificationFactory::make('sms');
و خودش لازم نیست بداند Object چگونه ساخته میشود.
# ساخت یک Factory ساده در PHP
ابتدا Interface:
interface Notification
{
public function send(string $message): void;
}
سپس SMS:
class SmsNotification implements Notification
{
public function send(string $message): void
{
echo "SMS: {$message}";
}
}
Email:
class EmailNotification implements Notification
{
public function send(string $message): void
{
echo "Email: {$message}";
}
}
حالا Factory:
class NotificationFactory
{
public static function make(string $type): Notification
{
return match ($type) {
'sms' => new SmsNotification(),
'email' => new EmailNotification(),
default => throw new InvalidArgumentException(
"Unknown notification type: {$type}"
),
};
}
}
حالا استفاده:
$notification = NotificationFactory::make('sms');
$notification->send('سلام، سفارش شما ثبت شد.');
یا:
$notification = NotificationFactory::make('email');
$notification->send('سفارش شما ثبت شد.');
مزیت اصلی این است که کدی که از Notification استفاده میکند، دیگر مستقیماً مسئول ایجاد Class مناسب نیست.
# Factory Pattern چگونه کار میکند؟
ساختار کلی را میتوان اینگونه تصور کرد:
نوع Object
│
▼
NotificationFactory
│
┌─────────┴─────────┐
▼ ▼
SmsNotification EmailNotification
│ │
└─────────┬─────────┘
▼
Notification
Factory بر اساس شرایط مشخص تصمیم میگیرد کدام Object ساخته شود.
# چرا Interface در Factory Pattern مهم است؟
استفاده از Interface در Factory اجباری نیست، اما در بسیاری از طراحیهای حرفهای بسیار مفید است.
مثلاً:
interface PaymentGateway
{
public function pay(int $amount): void;
}
چند Gateway:
class ZarinpalGateway implements PaymentGateway
{
public function pay(int $amount): void
{
echo "Pay with Zarinpal";
}
}
و:
class IdPayGateway implements PaymentGateway
{
public function pay(int $amount): void
{
echo "Pay with IDPay";
}
}
Factory:
class PaymentGatewayFactory
{
public static function make(string $gateway): PaymentGateway
{
return match ($gateway) {
'zarinpal' => new ZarinpalGateway(),
'idpay' => new IdPayGateway(),
default => throw new InvalidArgumentException(
'Unsupported payment gateway'
),
};
}
}
حالا:
$gateway = PaymentGatewayFactory::make('zarinpal');
$gateway->pay(500000);
کدی که از $gateway استفاده میکند، فقط PaymentGateway را میشناسد.
این موضوع باعث کاهش وابستگی به Implementationهای خاص میشود.
# Simple Factory در PHP چیست؟
چیزی که در مثالهای بالا ساختیم معمولاً با عنوان Simple Factory شناخته میشود.
Simple Factory یک Pattern رسمی از دستهبندی معروف GoF به همان معنای دقیق Factory Method نیست، اما یک تکنیک رایج برای متمرکز کردن منطق ایجاد Object است.
مثلاً:
class ReportFactory
{
public static function make(string $type): Report
{
return match ($type) {
'pdf' => new PdfReport(),
'excel' => new ExcelReport(),
default => throw new InvalidArgumentException(
'Invalid report type'
),
};
}
}
استفاده:
$report = ReportFactory::make('pdf');
$report->generate();
برای پروژههای کوچک و متوسط این روش میتواند کاملاً مناسب باشد.
# Factory Method در PHP چیست؟
Factory Method یک الگوی طراحی شناختهشدهتر است که در آن ایجاد Object به متدهایی واگذار میشود که میتوانند در Classهای فرزند Override شوند.
برای مثال:
abstract class NotificationCreator
{
abstract protected function createNotification(): Notification;
public function send(string $message): void
{
$notification = $this->createNotification();
$notification->send($message);
}
}
حالا Creator مربوط به SMS:
class SmsNotificationCreator extends NotificationCreator
{
protected function createNotification(): Notification
{
return new SmsNotification();
}
}
و Email:
class EmailNotificationCreator extends NotificationCreator
{
protected function createNotification(): Notification
{
return new EmailNotification();
}
}
استفاده:
$creator = new SmsNotificationCreator();
$creator->send('کد تایید شما 1234 است.');
در اینجا Class پایه مشخص میکند که فرآیند کلی چگونه انجام شود، اما Class فرزند مشخص میکند چه Objectی ساخته شود.
# تفاوت Simple Factory و Factory Method
این دو مفهوم بسیار به هم نزدیک هستند اما دقیقاً یکسان نیستند.
| ویژگی | Simple Factory | Factory Method |
|---|---|---|
| ساختار | معمولاً یک Factory Class | معمولاً متد Factory در ساختار وراثت |
| استفاده از Inheritance | معمولاً ندارد | دارد |
| پیچیدگی | کمتر | بیشتر |
| مناسب پروژه ساده | بله | معمولاً بیش از نیاز |
| انعطافپذیری | متوسط | بیشتر |
| پیادهسازی | ساده | ساختاریافتهتر |
برای پروژههای معمولی، Simple Factory میتواند کافی باشد.
# Factory Pattern برای سیستم پرداخت
یکی از کاربردهای بسیار خوب Factory، سیستم پرداخت است.
فرض کنید سه درگاه داریم:
PaymentGateway
├── Zarinpal
├── IDPay
└── Mellat
Interface:
interface PaymentGateway
{
public function pay(int $amount): void;
}
Factory:
class PaymentGatewayFactory
{
public static function make(string $driver): PaymentGateway
{
return match ($driver) {
'zarinpal' => new ZarinpalGateway(),
'idpay' => new IdPayGateway(),
'mellat' => new MellatGateway(),
default => throw new InvalidArgumentException(
"Unsupported gateway: {$driver}"
),
};
}
}
حالا:
$gateway = PaymentGatewayFactory::make('zarinpal');
$gateway->pay(750000);
اگر در آینده یک درگاه جدید اضافه شود، میتوانیم Implementation جدیدی بسازیم.
# Factory Pattern برای سیستم Notification
فرض کنید برنامه ما از چند روش ارسال پیام پشتیبانی میکند:
- SMS
- Push Notification
Interface:
interface Notification
{
public function send(string $message): void;
}
Factory:
class NotificationFactory
{
public static function make(string $type): Notification
{
return match ($type) {
'sms' => new SmsNotification(),
'email' => new EmailNotification(),
'push' => new PushNotification(),
default => throw new InvalidArgumentException(
"Unknown notification type: {$type}"
),
};
}
}
حالا:
$notification = NotificationFactory::make('push');
$notification->send('یک پیام جدید دارید.');
این ساختار برای سیستمهایی که چند Implementation مشابه دارند بسیار کاربردی است.
# Factory Pattern برای Export فایل
فرض کنید سیستم ما میتواند گزارش را به چند فرمت خروجی بگیرد:
Report
├── PDF
├── Excel
└── CSV
Interface:
interface ReportExporter
{
public function export(array $data): void;
}
پیادهسازی:
class PdfExporter implements ReportExporter
{
public function export(array $data): void
{
echo "Export PDF";
}
}
class ExcelExporter implements ReportExporter
{
public function export(array $data): void
{
echo "Export Excel";
}
}
Factory:
class ReportExporterFactory
{
public static function make(string $format): ReportExporter
{
return match ($format) {
'pdf' => new PdfExporter(),
'excel' => new ExcelExporter(),
default => throw new InvalidArgumentException(
"Unsupported format: {$format}"
),
};
}
}
استفاده:
$exporter = ReportExporterFactory::make('pdf');
$exporter->export($data);
# Factory Pattern و Dependency Injection
Factory و Dependency Injection میتوانند در کنار یکدیگر استفاده شوند.
برای مثال Factory میتواند Object مناسب را ایجاد کند، در حالی که Dependencyهای آن Object از طریق Constructor Injection وارد شوند.
مثلاً:
class ZarinpalGateway implements PaymentGateway
{
public function __construct(
private string $merchantId
) {
}
public function pay(int $amount): void
{
// Payment logic
}
}
Factory:
class PaymentGatewayFactory
{
public function __construct(
private string $merchantId
) {
}
public function make(string $driver): PaymentGateway
{
return match ($driver) {
'zarinpal' => new ZarinpalGateway(
$this->merchantId
),
default => throw new InvalidArgumentException(
'Unsupported gateway'
),
};
}
}
این روش نسبت به ساختن مستقیم Dependencyها داخل کلاسها انعطاف بیشتری دارد.
# Factory Pattern و SOLID
Factory میتواند در طراحی بهتر Objectها کمک کند، اما به تنهایی به معنی رعایت SOLID نیست.
برای مثال اگر Factory به یک Class بسیار بزرگ با صدها شرط تبدیل شود، خودش میتواند به یک مشکل معماری تبدیل شود.
استفاده درست از Factory باید با اصولی مانند:
- Single Responsibility
- Open/Closed
- Dependency Inversion
هماهنگ باشد.
مقاله SOLID در PHP که در همین کلاستر قرار دارد، پیشنیاز بسیار خوبی برای درک این موضوع است.
# Factory Pattern در Laravel
در Laravel نیز مفهوم Factory را زیاد مشاهده خواهید کرد.
یکی از معروفترین نمونهها Model Factory است.
برای مثال:
User::factory()->create();
Factoryهای Laravel معمولاً برای تولید دادههای تست و توسعه استفاده میشوند.
اما در پروژههای واقعی Laravel نیز میتوانیم Factoryهای اختصاصی خودمان را ایجاد کنیم.
مثلاً:
class PaymentGatewayFactory
{
public function make(string $driver): PaymentGateway
{
return match ($driver) {
'zarinpal' => app(ZarinpalGateway::class),
'idpay' => app(IdPayGateway::class),
default => throw new InvalidArgumentException(
'Unsupported gateway'
),
};
}
}
استفاده از Container در اینجا کمک میکند Dependencyهای Gatewayها نیز توسط Laravel مدیریت شوند.
# آیا Factory همیشه بهتر از new است؟
خیر.
این نکته بسیار مهم است.
اگر فقط یک Class ساده داریم:
$user = new User();
هیچ دلیلی وجود ندارد که برای آن Factory بسازیم.
Factory زمانی ارزش پیدا میکند که:
- چند Implementation داریم.
- انتخاب Object بر اساس شرایط انجام میشود.
- ساخت Object پیچیده است.
- منطق ایجاد Object در چند نقطه تکرار شده است.
- میخواهیم وابستگی کد به Implementationها کمتر شود.
پس:
new User();
در بسیاری از مواقع کاملاً درست است.
# چه زمانی نباید از Factory Pattern استفاده کنیم؟
اگر ساخت Object بسیار ساده است و فقط یک Implementation داریم، استفاده از Factory ممکن است پیچیدگی غیرضروری ایجاد کند.
مثلاً:
$user = new User();
تبدیل کردن آن به:
$user = UserFactory::make();
لزوماً هیچ مزیتی ندارد.
اگر Factory فقط یک Wrapper برای new باشد و مسئلهای را حل نکند، احتمالاً نیازی به آن نداریم.
# اشتباهات رایج در استفاده از Factory
Factory بسیار بزرگ
اگر کدی شبیه این ایجاد شود:
if (...)
{
...
}
elseif (...)
{
...
}
elseif (...)
{
...
}
و تعداد حالتها دائماً افزایش پیدا کند، باید معماری را دوباره بررسی کنیم.
قرار دادن منطق تجاری در Factory
Factory بهتر است بیشتر مسئول ساخت Object باشد.
قرار دادن منطق سنگین Business Logic داخل آن، مسئولیت Factory را بیش از حد افزایش میدهد.
استفاده از Factory برای همه Classها
Factory یک ابزار است، نه قانون.
قرار نیست برای هر Class یک Factory بسازیم.
استفاده از Stringهای پراکنده
اگر در پروژه دهها بار بنویسیم:
PaymentGatewayFactory::make('zarinpal');
و این Stringها بدون ساختار در تمام پروژه پخش شوند، مدیریت آنها دشوارتر میشود.
در پروژههای بزرگ میتوان از Enum یا Configuration برای مدیریت Driverها استفاده کرد.
مثلاً:
enum PaymentDriver: string
{
case ZARINPAL = 'zarinpal';
case IDPAY = 'idpay';
}
سپس:
PaymentGatewayFactory::make(
PaymentDriver::ZARINPAL->value
);
# مزایای Factory Pattern
Factory میتواند مزایای زیر را داشته باشد:
- جدا کردن منطق ایجاد Object
- کاهش وابستگی به Implementation
- سادهتر شدن کد مصرفکننده
- متمرکز شدن فرآیند ساخت Object
- افزایش قابلیت توسعه
- مناسب بودن برای چند Implementation
- امکان مدیریت Creation Logic در یک محل
# معایب Factory Pattern
در مقابل، Factory میتواند معایبی هم داشته باشد:
- افزایش تعداد Classها
- افزایش Abstraction
- پیچیده شدن پروژههای کوچک
- امکان تبدیل شدن Factory به یک Class بزرگ
- نیاز به طراحی مناسب در پروژههای بزرگ
بنابراین Factory زمانی مفید است که پیچیدگی ایجاد Object واقعاً وجود داشته باشد.
# Factory یا Dependency Injection؟
این دو مفهوم جایگزین کامل یکدیگر نیستند.
Dependency Injection
برای مدیریت وابستگیهای Class استفاده میشود.
مثلاً:
class OrderService
{
public function __construct(
private PaymentGateway $gateway
) {
}
}
Factory
برای تصمیمگیری و ایجاد یک Object مناسب استفاده میشود.
مثلاً:
$gateway = PaymentGatewayFactory::make('zarinpal');
در پروژههای واقعی میتوان هر دو را در کنار یکدیگر استفاده کرد.
# Factory یا Strategy؟
این دو Pattern نیز ممکن است با یکدیگر اشتباه گرفته شوند.
Factory بیشتر درباره این سؤال است:
> کدام Object را ایجاد کنم؟
اما Strategy بیشتر درباره این سؤال است:
> کدام الگوریتم یا رفتار را استفاده کنم؟
مثلاً:
Factory
↓
ایجاد ZarinpalGateway
در مقابل:
Strategy
↓
انتخاب روش محاسبه تخفیف
البته در معماریهای واقعی این دو میتوانند با هم ترکیب شوند.
# Factory Pattern در معماری یک پروژه واقعی
فرض کنید یک فروشگاه اینترنتی داریم:
app/
├── Contracts/
│ └── PaymentGateway.php
│
├── Payments/
│ ├── ZarinpalGateway.php
│ ├── IdPayGateway.php
│ └── MellatGateway.php
│
└── Factories/
└── PaymentGatewayFactory.php
این ساختار باعث میشود مسئولیتها تا حد زیادی جدا باشند.
PaymentGateway
↑
│
┌─────┼─────────┐
│ │ │
Zarinpal IDPay Mellat
↑
│
PaymentGatewayFactory
در یک پروژه Laravel بزرگتر میتوان این ساختار را بر اساس معماری واقعی پروژه به شکلهای مختلف سازماندهی کرد.
مهمتر از نام پوشهها، جدا بودن مسئولیتها و وابستگیها است.
# چگونه یک Factory خوب طراحی کنیم؟
هنگام ساخت Factory به این نکات توجه کنید:
1. مسئولیت Factory را محدود نگه دارید
وظیفه اصلی آن ایجاد یا انتخاب Object باشد.
2. از Interface استفاده کنید
اگر چند Implementation مشابه دارید، Interface میتواند قرارداد مشترک ایجاد کند.
3. Dependencyها را Hard-Code نکنید
در پروژههای بزرگ، Dependency Injection و Container میتوانند کمک زیادی کنند.
4. خطای Invalid Type را مدیریت کنید
مثلاً:
throw new InvalidArgumentException(
"Unsupported payment gateway"
);
5. از Factory برای سادهتر کردن معماری استفاده کنید
اگر Factory کد را پیچیدهتر میکند، احتمالاً باید دوباره بررسی شود که واقعاً به آن نیاز دارید یا خیر.
# جمعبندی Factory Pattern در PHP
Factory Pattern یکی از Patternهای مهم در برنامهنویسی شیگرا و طراحی نرمافزار است.
هدف اصلی آن این است که منطق ایجاد Object را از کدی که از Object استفاده میکند جدا کند.
مهمترین مفاهیمی که در این مقاله یاد گرفتیم:
- Factory Pattern چیست؟
- چرا Factory استفاده میشود؟
- Simple Factory چیست؟
- Factory Method چیست؟
- استفاده از Interface در Factory
- Factory برای سیستم پرداخت
- Factory برای Notification
- Factory برای Export
- رابطه Factory و Dependency Injection
- رابطه Factory و SOLID
- Factory در Laravel
- تفاوت Factory و Strategy
- زمان مناسب و نامناسب استفاده از Factory
به خاطر داشته باشید که Factory قرار نیست جایگزین new در تمام پروژه شود.
اگر ایجاد Object ساده است:
$user = new User();
کاملاً کافی است.
اما زمانی که فرآیند ایجاد Object پیچیده میشود یا چند Implementation مختلف داریم، Factory میتواند ساختار کد را بسیار تمیزتر و قابل توسعهتر کند.
# سوالات متداول Factory Pattern در PHP
Factory Pattern در PHP چیست؟
Factory Pattern یک الگوی طراحی برای جدا کردن منطق ایجاد Object از بخشی از برنامه است که از آن Object استفاده میکند.
آیا Factory Pattern یکی از Design Patternهای PHP است؟
بله. Factory Method یکی از الگوهای شناختهشده طراحی شیگرا است و مفهوم Simple Factory نیز بهعنوان یک تکنیک رایج برای متمرکز کردن فرآیند ایجاد Object استفاده میشود.
تفاوت Factory و Factory Method چیست؟
Simple Factory معمولاً یک Class یا متد متمرکز برای انتخاب و ساخت Objectهاست، اما Factory Method ساخت Object را به متدهایی واگذار میکند که میتوانند توسط Classهای فرزند تغییر داده شوند.
آیا استفاده از Factory همیشه ضروری است؟
خیر. اگر ساخت Object ساده باشد و فقط یک Implementation داشته باشید، استفاده مستقیم از new معمولاً سادهتر و مناسبتر است.
Factory Pattern چه کاربردی در Laravel دارد؟
Factory در Laravel کاربردهای مختلفی دارد. Model Factory یکی از نمونههای شناختهشده آن برای ایجاد دادههای آزمایشی است. همچنین میتوان Factoryهای اختصاصی برای سرویسهایی مانند پرداخت، Notification یا Export طراحی کرد.
آیا Factory با Dependency Injection قابل استفاده است؟
بله. Factory میتواند انتخاب و ایجاد Object را انجام دهد و Dependency Injection نیز وابستگیهای Object ساختهشده را مدیریت کند.
آیا Factory Pattern باعث رعایت SOLID میشود؟
خیر. Factory به خودی خود تضمینکننده SOLID نیست؛ اما اگر درست طراحی شود میتواند به کاهش Coupling و بهبود بعضی از اصول SOLID کمک کند.
Factory بهتر است یا Strategy؟
این دو برای مسائل متفاوتی هستند. Factory بیشتر روی ایجاد و انتخاب Object تمرکز دارد، در حالی که Strategy برای تعویض الگوریتم یا رفتار استفاده میشود.
# لینکسازی داخلی پیشنهادی
این مقاله باید به مقاله جامع Design Pattern در PHP لینک داده شود:
Design Pattern در PHP چیست؟ آموزش الگوهای طراحی با مثال عملی
همچنین لینکهای زیر از مقالات قبلی کلاستر OOP مناسب هستند:
- Interface در PHP
- Abstract Class در PHP
- Dependency Injection در PHP
- SOLID در PHP
- Inheritance در PHP
- Class و Object در PHP
مقالههای فرزند پیشنهادی بعد از این مقاله:
Design Pattern در PHP
│
├── Factory Pattern در PHP ← این مقاله
├── Singleton Pattern در PHP
├── Builder Pattern در PHP
├── Adapter Pattern در PHP
├── Decorator Pattern در PHP
├── Facade Pattern در PHP
├── Strategy Pattern در PHP
├── Observer Pattern در PHP
└── Command Pattern در PHP





