آشنایی با Singleton و Factory Pattern در PHP با مثال عملی
اگه چند سالی هست که با PHP کار کرده میکنی، حتماً متوجه شدی که باید یه دستی به سر و روی معماری کدت بکشی و کدهات رو تر تمیز و جمع و جور تر کنی. اینجاست که Design Pattern ها مطرح میشن و حسابی بهت میکنن و خیلی از مشکلاتت هم حل میشه. تو این مطلب، دو تا از پر کاربر ترین دیزاین پترن ها (یعنی Singleton و Factory) رو با مثالهای واقعی توی PHP باهم مرور میکنیم. هدف اینه که واضح بفهمی هر کدوم دقیقاً چه مشکلی رو حل میکنن و چه زمانی باید از این دو تا دیزاین پترن استفاده کنی.
بخش اول: Singleton چیه و چه زمانی به درد میخوره؟
توی php شما هر بار که یک کلاس رو new میکنید، رم سرور درگیر میشه، برای همین این معماری بوجود اومده تا منابع سرور شما الکی همیشه درگیر نشه. Singleton یعنی از یک کلاس فقط یک نمونه در طول اجرای برنامه داشته باشیم. این الگو برای مدیریت یسری کلاس ها مثل اتصال به دیتابیس یا خواندن تنظیمات (Config) خیلی کاربردیه.
پیادهسازی استاندارد Singleton در PHP
<?php
final class Database
{
private static ?Database $instance = null;
private PDO $pdo;
private function __construct(array $config)
{
$dsn = sprintf('mysql:host=%s;dbname=%s;charset=%s',
$config['host'], $config['dbname'], $config['charset'] ?? 'utf8mb4'
);
$this->pdo = new PDO($dsn, $config['user'], $config['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
}
public static function getInstance(array $config = []): Database
{
if (self::$instance === null) {
if (empty($config)) {
// تنظیمات پیشفرض برای نمونه
$config = [
'host' => 'localhost',
'dbname' => 'app',
'user' => 'root',
'pass' => '',
'charset' => 'utf8mb4',
];
}
self::$instance = new self($config);
}
return self::$instance;
}
private function __clone() {}
public function __wakeup(): void
{
throw new Exception('Cannot unserialize a singleton.');
}
public function pdo(): PDO
{
return $this->pdo;
}
}
// استفاده:
$db = Database::getInstance([ 'host' => '127.0.0.1', 'dbname' => 'shop', 'user' => 'app', 'pass' => 'secret' ]);
$stmt = $db->pdo()->query('SELECT NOW() as now');
$time = $stmt->fetch()['now'];
نکات مرتبط با Singleton
- استفاده از Singleton باعث میشه روی یه منبع مشترک مثل اتصال دیتابیس کنترل کاملاً یکدست داشته باشی و لازم نباشه هر بار نمونهٔ جدید بسازی، اینطوری هم کدت مرتبتر میمونه هم منابع الکی مصرف نمیشن.
- از اون طرف، چون Singleton حالت سراسری داره، تستنویسی و مدیریت وابستگیها سختتر میشه. بهتره مستقیم ازش استفاده نکنی و دسترسی بهش رو از طریق لایههایی مثل Service Container انجام بدی تا بتونی راحتتر تست یا جایگزینش کنی.
- توی PHP هر درخواست وب یه چرخهٔ جدا داره، پس نگرانی بابت Thread-Safety مثل زبانهای چندریسمانی وجود نداره. با این حال، Singleton رو فقط برای منابعی استفاده کن که در طول اجرای برنامه فقط یکبار new بشن کفایت میکنه، نه برای هر چیزی. (مثلا دیتابیس یکبار باید new بشه و بسه، هر بار new شدنش برای م آورده ای نداره حالا جلوتر بیشتر بررسی میکنیم)
بخش دوم: Factory Pattern به زبان ساده
Factory یعنی ساختن آبجکت ها رو بذاریم پای یک سازنده مرکزی تا بقیهٔ بخش های برنامه درگیر جزئیات ساخت نشَن. این کار باعث میشه وابستگی ها کمتر و تغییرات آینده راحت تر مدیریت بشه.
مثال عملی: سیستم اعلانها (Email/SMS/Push)
<?php
interface Notifier
{
public function send(string $to, string $message): void;
}
class EmailNotifier implements Notifier
{
public function send(string $to, string $message): void
{
// ارسال ایمیل (نمونه ساده)
echo "Email to {$to}: {$message}\n";
}
}
class SmsNotifier implements Notifier
{
public function send(string $to, string $message): void
{
echo "SMS to {$to}: {$message}\n";
}
}
class PushNotifier implements Notifier
{
public function send(string $to, string $message): void
{
echo "Push to {$to}: {$message}\n";
}
}
final class NotifierFactory
{
public static function make(string $type): Notifier
{
return match (strtolower($type)) {
'email' => new EmailNotifier(),
'sms' => new SmsNotifier(),
'push' => new PushNotifier(),
default => throw new InvalidArgumentException('Unknown notifier type: ' . $type)
};
}
}
// استفاده
$notifier = NotifierFactory::make('email');
$notifier->send('user@example.com', 'خوش اومدی!');
مزایای Factory
- پنهان سازی جزئیات ساخت آبجکت و سادهتر شدن کدهای لایه های بالاتر.
- تستپذیری بهتر: میتونی نسخه های Mock از Interface بسازی و تزریق کنی.
- انعطافپذیری: اضافه کردن نوع جدید (مثلاً WebhookNotifier) فقط به Factory و کلاس جدید مربوطه ربط داره.
بخش سوم: ترکیب این دو در یک سناریوی واقعی
فرض کن یه فروشگاه آنلاین داری. میخوای: (۱) اتصال دیتابیس رو یکبار بسازی (Singleton) و (۲) اعلانها رو بر اساس تنظیمات کاربر ارسال کنی (Factory).
ساخت یک سناریوی کامل
<?php
final class Config
{
private static ?Config $instance = null;
private array $items;
private function __construct(array $items)
{
$this->items = $items;
}
public static function getInstance(array $items = []): Config
{
if (!self::$instance) {
self::$instance = new self($items ?: [
'db' => [
'host' => '127.0.0.1', 'dbname' => 'shop', 'user' => 'app', 'pass' => 'secret',
'charset' => 'utf8mb4'
],
'notifications' => [ 'default' => 'email' ]
]);
}
return self::$instance;
}
public function get(string $key, mixed $default = null): mixed
{
return array_reduce(explode('.', $key), fn($carry, $k) => $carry[$k] ?? $default, $this->items);
}
}
// استفاده ترکیبی
$config = Config::getInstance();
$db = Database::getInstance($config->get('db'));
$type = $config->get('notifications.default', 'email');
$notifier = NotifierFactory::make($type);
// سفارش جدید ثبت شد
$notifier->send('customer@example.com', 'سفارش شما ثبت شد!');
اینجا Config و Database بهصورت Singleton اداره میشن و انتخاب نوع اعلان با Factory انجام میشه. اگر فردا تصمیم بگیری اعلان پیش فرض رو از ایمیل به پیامک تغییر بدی، فقط کافیه تو Config مقدارش رو عوض کنی؛ بقیهٔ کد بدون تغییر کار میکنه.
بخش چهارم: مقایسه سریع و نکات معماری
| Pattern | هدف | کِی استفاده کنیم | ریسکها |
|---|---|---|---|
| Singleton | یک نمونه یکتا از یک منبع مشترک | اتصال DB، Config، Logger (در صورت نیاز) | سختی تست، وضعیت سراسری، وابستگی مخفی |
| Factory | جدا کردن منطق ساخت آبجکت از مصرفکننده | وقتی چند پیادهسازی برای یک Interface داریم | پیچیدگی اضافه اگر فقط یک نوع داریم |
نکتههایی برای استفاده درست از Singleton و Factory
- همیشه اول یه Interface بساز که قرارداد کلی رفتار رو مشخص کنه، بعد کلاسهایی رو بنویس که اون رو پیادهسازی کنن. اینطوری Factory فقط با Interface کار میکنه و لازم نیست از جزئیات پیادهسازی خبر داشته باشه.
- حتی اگه از Singleton استفاده میکنی، سعی کن وابستگیها رو مستقیم (با تزریق از بیرون) به کلاس بدی. این کار باعث میشه تست کردن و تغییر دادن اجزا خیلی راحتتر بشه.
- Singleton رو فقط وقتی استفاده کن که واقعاً لازمه؛ مثلاً وقتی ساختن یه کلاس گرونه یا باید فقط یه نسخه ازش وجود داشته باشه. اگه کلاس سادهست یا وضعیت سراسری لازم نداره، Singleton فقط پیچیدگی اضافه میکنه.
- توی کلاسهای Factory لاگ بگیر تا بدونی چه نوع آبجکتهایی بیشتر ساخته میشن. اگه یه رفتار عجیب دیدی، این لاگها کمک میکنن سریع بفهمی مشکل از کجاست.
جمعبندی
Singleton و Factory از اون الگوهایی هستن که اگه درست و بهجا استفاده بشن، معماری پروژه رو تمیزتر و توسعه و نگهداری رو سادهتر میکنن. Singleton برای مدیریت منابع مشترک عالیه، اما باید حواست باشه تو دام وضعیت سراسری و سختی تست نیفتی. Factory هم دستت رو برای تغییر و گسترش انواع پیادهسازیها باز میذاره و وابستگیها رو از مصرفکنندهها دور نگه میداره.
تجربهٔ خودت با این دو الگو چی بوده؟ اگه مثال جالبی تو پروژههات داری یا جایی به مشکل خوردی، توی کامنتها برام بنویس تا با هم بررسیش کنیم.
ثبت دیدگاه
اگه در مورد این مطلب نظری داری یا در همین موضوع سوالی داری، همینجا مطرح کن تا از دیدگاه ارزشمندت استفاده کنیم و انرژی بگیریم، یا سوالت رو جواب بدیم
در ضمن، شماره موبایلت تو سایت نمایش داده نمیشه و پیش ما به صورت محرمانه میمونه
کد تایید پیامک شده به شماره را وارد نمایید

دیدگاه شما