که چی؟

بعضی وقت‌ها نیاز میشه که از کلاسی که استفاده میکنیم، در حال اجرا، فقط یه نمونه داشته باشیم. برای مثال:
  • در حال نوشتن برنامه‌ای هستیم که در اون در چند جای برنامه با پایگاه داده ارتباط برقرار می‌کنیم. در اینجا به جای اینکه هر بار که خواستیم با پایگاه داده ارتباط داشته باشیم، یک نمونه از کلاس پایگاه داده ایجاد کنیم، خوبه که یک نمونه ایجاد کنیم و هر بار از همون استفاده کنیم، اینطور در منابع صرفه‌جویی زیادی میشه.
  • در حال نوشتن برنامه‌ی جهان خلقت هستیم. در اینجا موقع نوشتن کلاس مربوط به خدا، باید توجه کنیم که اگه در آن واحد، دو نمونه از کلاس خدا ایجاد شه، ممکن‌ه که هر کدوم از خداها روی کل برنامه تاثیر بذارن و آشفتگی به وجود بیاد. اینجا باید دقت کنیم که از روی کلاس خدا، در صورت نیاز، فقط یک نمونه باید داشته باشیم.
  • در حال نوشتن برنامه‌ای هستیم که در آن، برنامه چند زبانه‌ست. یعنی با توجه به تنظیمات کاربر، ممکن‌ه که محیط برنامه فارسی باشه و یا انگلیسی. در اینجا به جای اینکه هر بار که نیاز شد، فایل زبان رو باز کنیم و رشته‌های مرتبط با زبان رو بخونیم و قسمت مورد نیاز رو جدا کنیم، میشه یک نمونه از کلاس مربوط به زبان ایجاد کرد و هر بار از همون برای گرفتن ترجمه‌ها استفاده کرد.
  • در حال نوشتن بازی ای هستیم که قراره زندگی واقعی رو شبیه‌سازی کنه و اون رو میخوایم برای تست به زوج‌های جوان بدیم. اینجا باید دقت کنیم که برای هر فرد، فقط یک نمونه از کلاس همسر باید ساخته شه و اگه این مورد رو رعایت نکنیم، ممکن‌ه در زندگی واقعی اون زوج، باعث یه طلاق شیم :دی.

در این نوشته، قرار بر اون‌ه که ببینیم چطور میشه اینطور کلاسی رو داشت :).

من و بارون و سینگلتون!

در سرتاسر مثال‌های بالا، ما میتونیم از مدلی استفاده کنیم که به اون مدل سینگلتون میگن. مدل سینگلتون به هر کلاس اجازه میده که حداکثر فقط یک نمونه از خودش رو داشته باشه. کلیت این مدل اینه: «موقع ساخت شی، چک کن ببین قبلش نمونه‌ای ازش رو نداشته باشی.». طبیعتا وقتی میخوایم چک کنیم که قبلا نمونه‌ای از این کلاس داریم یا نه، باید از متغیری استفاده کنیم که بین همه‌ی شی‌ها اشتراکی باشه و در نتیجه‌ی این متغیر باید از نوع استاتیک باشه. یک نمونه ساده‌ی این کلاس رو میشه در زیر دید:

class Singleton
{
    protected static $instance = NULL;
    private $name;
    
    public static function getInstance()
    {
        if (self::$instance === NULL)
        {
            self::$instance = new Singleton();
        }
        
        return self::$instance;
    }
    
    private function __construct()
    {
        
    }
    
    private function __clone()
    {

    }
    
    public function getName()
    {
        return $this->name;
    }
    
    public function setName($name)
    {
        $this->name = $name;
        return $this;
    }
};

در کلاس بالا اومدیم یه متغیر به اسم instance از نوع استاتیک تعریف کردیم و بهش مقدار پوچ -به این معنی که نه اون رو غلط فرض کردیم و نه درست! فقط بهش گفتیم تو اول کار بی‌تفاوت باش- دادیم. بعد در تابع استاتیک getInstance اومدیم گفتیم اگه این مقدار پوچ بود -یعنی تا حالا کسی بهش کاری نداشته- یک نمونه از کلاس رو بساز و بنداز توش و در آخر کار هم این مقدار رو برگردون. حالا وقتی بخوایم یک نمونه از این کلاس رو داشته باشیم، دو حالت داره، یا این مقدار استاتیک همچنان پوچ‌ه که یک نمونه از کلاس ساخته میشه و در اون ریخته میشه و در نهایت برگردونده میشه، و یا از قبل در این متغیر ما چیزهایی داشتیم -که در حقیقت همون نمونه کلاس هست- که در نتیجه اون نمونه رو به جای ساختن نمونه‌ی جدید، برمیگردونیم. پس در هر صورت، ما حداکثر یک نمونه از این کلاس رو در آن واحد میتونیم داشته باشیم. برای مثال، یک نمونه‌ی استفاده از این کلاس به این صورت‌ه:

$a = Singleton::getInstance();
$b = Singleton::getInstance();

$a->setName("Meysam");
echo $a->getName() . "\n";

$b->setName("Another Meysam");

echo $a->getName() . "\n";

و اجرای اون:

meysam@freedom:~/www/testFunc$ php singleton.php 
Meysam
Another Meysam

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

پدری برای همه

تا اینجا نیازی که در چهار مثال ابتدایی داشتیم رفع میشه. اما هنوز میشه شی‌گراتر به مسئله نگاه کرد: «چه جالب میشه اگه بتونیم یک کلاس پدر داشته باشیم تا برای داشتن کلاس سینگلتون، از اون مشتق بگیریم.»، هوم؟

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

class Foo extends Singleton
{

};

class Bar extends Singleton
{

};

و ساختن نمونه از دوتا کلاس:

$a = Foo::getInstance();
$b = Bar::getInstance();

$a->setName("Meysam");
echo $a->getName() . "\n";

$b->setName("Another Meysam");

echo $a->getName() . "\n";

و اجرای اون:

meysam@freedom:~/www/testFunc$ php singleton_pattern.php 
Meysam
Another Meysam

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

پدری با خانواده

برای رفع مشکل مذکور، میایم به جای استفاده از یک متغیر استاتیک، از یک آرایه‌ی استاتیک استفاده می‌کنیم. اندیس رو هم میذاریم اسم کلاسی که اون تقاضای نمونه کرده است. پس کلاس سینگلتون‌مون رو به اینطور چیزی تغییر میدیم:

class Singleton
{
    protected static $instance = array();
    
    public static function getInstance()
    {
        $called_class = get_called_class();
        if (!isset(self::$instance[$called_class]))
        {
            self::$instance[$called_class] = new Singleton();
        }
        
        return self::$instance[$called_class];
    }
    
    private function __construct()
    {
        
    }
    
    private function __clone()
    {

    }
    
    public function getName()
    {
        return $this->name;
    }
    
    public function setName($name)
    {
        $this->name = $name;
        return $this;
    }
};

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

$a = Foo::getInstance();
$b = Bar::getInstance();

$a->setName("Meysam");
echo $a->getName() . "\n";

$b->setName("Another Meysam");

echo $a->getName() . "\n";
echo $b->getName() . "\n";

و در نهایت اجرا:

meysam@freedom:~/www/testFunc$ php singleton_pattern.php 
Meysam
Meysam
Another Meysam

خب این ظاهرا همون‌ه که میخوایم! ;). اجازه بدین کلاس‌های مشتق شده رو با سلیقه‌ی خودمون تغییر بدیم:

class Foo extends Singleton
{
    public function getName()
    {
        return $this->name . " :)";
    }
};

class Bar extends Singleton
{

};

و یک نمونه اجرا بگیریم:

meysam@freedom:~/www/testFunc$ php singleton_pattern.php 
Meysam
Meysam
Another Meysam

و نتیجه بگیریم که این همونی نیست که میخوایم! :دی مشکل از کجاست؟

چشم‌های پدری، نگاه یک پدر

مشکل کد بالا اینجاست که وقتی که مطمئن شدیم از کلاس مذکور نمونه‌ای نداریم، یک نمونه از کلاس سینگلتون رو ساختیم و گفتیم این نمونه‌ی تو، در حالیکه کلاسی که گفت برا من یه نمونه کلاس بساز، یه چیز دیگه بود! این مشکل رو هم با ساختن یک نمونه از کلاسی که تابع getInstance رو صدا زده، رفع می‌کنیم.

class Singleton
{
    protected static $instance = array();
    
    public static function getInstance()
    {
        $called_class = get_called_class();
        if (!isset(self::$instance[$called_class]))
        {
            self::$instance[$called_class] = new $called_class();
        }
        
        return self::$instance[$called_class];
    }
    
    private function __construct()
    {
        
    }
    
    private function __clone()
    {

    }
    
    public function getName()
    {
        return $this->name;
    }
    
    public function setName($name)
    {
        $this->name = $name;
        return $this;
    }
};

و گرفتن اجرا:

meysam@freedom:~/www/testFunc$ php singleton_pattern.php 
Meysam :)
Meysam :)
Another Meysam

اینجا دیگه به ته داستان میرسیم :).


پ.ن ۱: اگه تا اینجای این پست رو خوندین، احتمالا این حوصله رو هم دارین که این لینک رو بخونید. باشه جایزه مثلا :دی.
پ.ن ۲: می‌فرماد: «تا پدر نشی، نمی‌فهمی پدر یعنی چی.».