Singleton Pattern چیست؟

پوریا سبحانلو

پوریا سبحانلو

28 شهریور 1403
دقیقه 6
Singleton Pattern چیست؟
laravel

الگوی Singleton یکی از ساده‌ترین الگوهای طراحی Creational هست. این الگو تضمین می‌کنه که از یک کلاس فقط یک نمونه (instance) وجود داشته باشه و دسترسی به اون نمونه از طریق یک متد عمومی مثل getInstance() فراهم بشه.

1. تعریف و هدف Singleton

Singleton یه الگوی طراحی است که تضمین می‌کنه یک کلاس فقط یک نمونه (instance) داشته باشه و یه نقطه دسترسی سراسری (global access point) به اون نمونه فراهم کنه.

 

2. چرا Singleton مهم است؟

گاهی نیاز داریم که فقط یه نسخه از یک کلاس ساخته بشه، مثلاً مدیریت اتصال دیتابیس یا تنظیمات کلی برنامه.

جلوگیری از مصرف اضافی حافظه و مشکلاتی مثل ناسازگاری داده‌ها.

تسهیل کنترل روی منبعی که باید یکپارچه و یکتا باشه.

 

3. چطوری کار می‌کنه؟

سازنده (constructor) کلاس را private یا protected می‌کنیم تا از ساخت نمونه جدید جلوگیری کنیم.

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

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

 

چرا از Singleton استفاده می‌کنیم؟

جلوگیری از ایجاد چندین شیء از یک کلاس خاص.

کنترل دقیق روی منابع مشترک (مثل اتصال پایگاه داده، تنظیمات اپلیکیشن، یا کلاس‌های کش).

حفظ وضعیت (state) در طول عمر برنامه در جاهایی که نیازه.

 

class Logger
{
   private static $instance = null;
   private function __construct()
   {
       // اتصال به فایل لاگ یا هر چیز دیگر
   }
   public static function getInstance()
   {
       if (!self::$instance) {
           self::$instance = new Logger();
       }
       return self::$instance;
   }
   public function log($message)
   {
       echo "[LOG]: " . $message . PHP_EOL;
   }
}

راه استفاده:

$logger1 = Logger::getInstance();
$logger2 = Logger::getInstance();
$logger1->log("Hello Singleton!");

var_dump($logger1 === $logger2); // true

 

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

 

مثال Singleton در PHP (لاراولی)

class DatabaseConnection {
   private static $instance = null;
   private function __construct() {
       // اتصال به دیتابیس
   }
   public static function getInstance() {
       if (self::$instance === null) {
           self::$instance = new DatabaseConnection();
       }
       return self::$instance;
   }
}
// استفاده
$db1 = DatabaseConnection::getInstance();
$db2 = DatabaseConnection::getInstance();
var_dump($db1 === $db2); // bool(true)

گر در محیط‌هایی که چندین ترد (thread) دارند کار می‌کنید، باید مراقب باشید که ایجاد نمونه به صورت thread-safe انجام شود.

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

در زبان‌هایی مثل جاوا یا سی‌شارپ، به صورت رسمی و ساخت‌یافته‌تر توسط زبان پشتیبانی می‌شود.

 

7. موارد استفاده رایج Singleton

اتصال دیتابیس (Database connection pool)

Logger (ثبت وقایع برنامه)

Cache manager

تنظیمات کلی برنامه (Configuration manager)

 

8. چرا بعضی وقت‌ها Singleton بد است؟

گاهی می‌تواند به Anti-pattern تبدیل شود و به برنامه وابستگی غیرضروری اضافه کند.

تست‌پذیری را سخت می‌کند چون نمونه‌ها سخت جایگزین می‌شوند.

استفاده زیاد از Global State در برنامه که باعث پیچیدگی و خطا می‌شود.

 

کاربردهای Singleton کجاهاست؟

1. مدیریت اتصال به دیتابیس (Database Connection)

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

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

Singleton تضمین می‌کنه که فقط یک نمونه اتصال ساخته و مجددا استفاده بشه.

2. سیستم ثبت گزارش (Logging)

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

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

3. مدیریت تنظیمات برنامه (Configuration Manager)

تنظیمات برنامه معمولا باید یک‌جا ذخیره و مدیریت بشن.

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

4. کش (Cache)

وقتی می‌خوایم داده‌هایی که محاسبه یا واکشی شده رو ذخیره کنیم و در جای جای برنامه بهش دسترسی داشته باشیم.

Singleton به عنوان Cache Manager می‌تونه کار کنه.

5. کنترل دسترسی به سخت‌افزار یا منابع مشترک

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

6. مدیریت Thread Pool یا Connection Pool

برای بهینه‌سازی استفاده از منابع در برنامه‌های چند نخی یا برنامه‌هایی که به تعداد زیادی اتصال نیاز دارند.

 

 

خوب الان با هم بریم یه مثال عملی از پیاده‌سازی حرفه‌ای و واقعی از Singleton Pattern برای ارسال SMS توی پروژه لاراول انجام بدیم.

  • ساخت کلاس SmsService
  • تعریف آن به‌صورت Singleton در AppServiceProvider
  • استفاده از آن در کنترلر یا جای دیگه
namespace App\Services;
class SmsService
{
   protected $client;
   public function __construct()
   {
       $this->client = new \SomeSms\Sdk([
           'api_key' => env('SMS_API_KEY'),
           'sender' => env('SMS_SENDER'),
       ]);
   }
   public function send(string $phone, string $message): bool
   {
       return $this->client->send($phone, $message);
   }
}

 

 

2.  تعریف سرویس به‌صورت Singleton در Service Container


در AppServiceProvider.php، در متد register() اینو اضافه کن:

use App\Services\SmsService;
public function register(): void
{
   $this->app->singleton(SmsService::class, function ($app) {
       return new SmsService();
   });
}

 

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

 

use App\Services\SmsService;
class UserController extends Controller
{
   public function sendOtp(Request $request, SmsService $sms)
   {
       $phone = $request->input('phone');
       $otp = rand(1000, 9999);
       
       $sms->send($phone, "کد تایید شما: $otp");
       return response()->json(['status' => 'ok']);
   }
}

 

اینم از  env فایل

SMS_API_KEY=your-api-key-here
SMS_SENDER=30001234

 

حالا برگدیم به کنترلرمون و متد  sendotp بررسی کنیم بیشتر

public function sendOtp(Request $request, SmsService $sms)


لاراول وقتی بخواد این متد رو اجرا کنه، اول می‌بینه که چه چیزی نیاز داره (Request, SmsService).

چون SmsService در AppServiceProvider به‌صورت singleton رجیستر شده:


$this->app->singleton(SmsService::class, function ($app) {
   return new SmsService();
});

لاراول متوجه میشه که چطور باید یک نسخه از SmsService بسازه یا بازیابی کنه.

بنابراین، بدون اینکه شما new SmsService() صدا بزنی، لاراول خودش هندل می‌کنه و اون instance رو به متد تزریق می‌کنه.

 


پوریا سبحانلو

پوریا سبحانلو

سلام من پوریام

یه php کاری که ریز نگاهی هم به فریم ورک های js داره


admoon من اینجام

اینارو هم یه نگاه بنداز