Singleton Pattern


ارسال در تاریخ ۰۱ مهر,۱۳۹۶



در این آموزش یاد می گیرید که چگونه الگوی طراحی Singleton را پیاده سازی کنید و چرا و چه زمانی از این الگوی طراحی در اپلیکیشن خود استفاده کنید. همانطور که از نام Singleton می توان فهمید توسط این متد می توان که یک و تنها یک نمونه از کلاس داشت.

بیایید ببینیم در دانشنامه ویکی پدیا در مورد این الگوی طراحی چه چیزی داریم:

الگوی طراحی Singleton یک الگوی طراحی است که دسترسی به کلاس را تنها به یک نمونه محدود می کند. این الگو برای زمانی که در کل اپلیکیشن به اعمال کلاس تنها به یک نمونه نیاز داریم مفید است.

همانطور که در بالا ذکر شده است، هنگامی که می خواهیم اطمینان داشته باشیم که یک و تنها یک نمونه نیاز است که برای هر کلاسی ایجاد شود، سپس از الگوی طراحی Singleton برای آن کلاس استفاده می کنیم.

ممکن است این سوال را بپرسید که چرا ما باید یک کلاس را ایجاد کنیم که تنها اجازه ایجاد یک نمونه از آن را داشته باشیم. موارد استفاده زیادی هست که می توانیم از این الگوی طراحی استفاده کنیم. این موارد شامل: کلاس های پیکربندی، کلاس های جلسه، کلاس های دیتابیس و موارد بیشتر

در این مقاله از کلاس دیتابیس استفاده می کنیم. اول بررسی می کنیم که اگر از الگوی طراحی Singleton استفاده نکنیم چه مشکلی می تواند بوجود بیاید.

مسئله : 

یک اتصال دیتابیس ساده تصور کنید. زمانی که یک نمونه از آن کلاس ایجاد می کنیم یک اتصال با دیتابیس ایجاد می شود.

class database {
     
    private $dbName = null, $dbHost = null, $dbPass = null, $dbUser = null;
     
    public function __construct($dbDetails = array()) {
         
        $this->dbName = $dbDetails['db_name'];
        $this->dbHost = $dbDetails['db_host'];
        $this->dbUser = $dbDetails['db_user'];
        $this->dbPass = $dbDetails['db_pass'];
 
        $this->dbh = new PDO('mysql:host='.$this->dbHost.';dbname='.$this->dbName, $this->dbUser, $this->dbPass);
         
    }
     
}

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

بنابراین ناخودآگاه توسعه دهنده دچار اشتباه می شود که کدام مورد تاثیر بسزایی در سرعت دیتابیس و اپلیکیشن سرور دارد.

بیایید موارد یکسانی را که به علت ایجاد نمونه های مختلف از آن کلاس داریم را ببینیم:

$dbDetails = array(
        'db_name' => 'designpatterns',
        'db_host' => 'localhost',
        'db_user' => 'root',
        'db_pass' => 'mysqldba'
);
 
$db1 = new database($dbDetails);
var_dump($db1);
$db2 = new database($dbDetails);
var_dump($db2);
$db3 = new database($dbDetails);
var_dump($db3);
$db4 = new database($dbDetails);
var_dump($db4);
 
// Output
object(database)[1]
  private 'dbName' => string 'designpatterns' (length=14)
  private 'dbHost' => string 'localhost' (length=9)
  private 'dbPass' => string 'mysqldba' (length=8)
  private 'dbUser' => string 'root' (length=4)
  public 'dbh' => object(PDO)[2]
object(database)[3]
  private 'dbName' => string 'designpatterns' (length=14)
  private 'dbHost' => string 'localhost' (length=9)
  private 'dbPass' => string 'mysqldba' (length=8)
  private 'dbUser' => string 'root' (length=4)
  public 'dbh' => object(PDO)[4]
object(database)[5]
  private 'dbName' => string 'designpatterns' (length=14)
  private 'dbHost' => string 'localhost' (length=9)
  private 'dbPass' => string 'mysqldba' (length=8)
  private 'dbUser' => string 'root' (length=4)
  public 'dbh' => object(PDO)[6]
object(database)[7]
  private 'dbName' => string 'designpatterns' (length=14)
  private 'dbHost' => string 'localhost' (length=9)
  private 'dbPass' => string 'mysqldba' (length=8)
  private 'dbUser' => string 'root' (length=4)
  public 'dbh' => object(PDO)[8]

 اگر خرجی کد بالا را ببینید، می توانید ببینید که هر نمونه یک شماره منبع جدید دارد که به آن اختصاص داده شده است، بنابراین همه نمونه ها به طور کامل ارجاعات جدید هستند، از این رو به هر کدام از این نمونه ها یک حافظه تخصیص داده شده است، بنابراین اپلیکیشن ما بطور ناخودآگاه منابعی را اشغال می کند که نیاز نداریم.

جواب :

این در کنترل ما نیست که توسعه دهنده چگونه از فریمورک پایه ما استفاده می کند. بعد از عمل مطالعه کد این در اختیار ما است، اما در طول توسعه ما نمی توانیم که تمام زمان را پشت سر آن ها بنشینیم.

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

هنگامی که این الگو را پیاده سازی می کنیم، هدف ایجاد یک و تنها یک نمونه از کلاس می باشد. اجازه بدهید که کد کلاس پایین را اضافه کنیم:

class database {
     
    private $dbName = null, $dbHost = null, $dbPass = null, $dbUser = null;
    private static $instance = null;
     
    private function __construct($dbDetails = array()) {
         
        // Please note that this is Private Constructor
         
        $this->dbName = $dbDetails['db_name'];
        $this->dbHost = $dbDetails['db_host'];
        $this->dbUser = $dbDetails['db_user'];
        $this->dbPass = $dbDetails['db_pass'];
 
        // Your Code here to connect to database //
        $this->dbh = new PDO('mysql:host='.$this->dbHost.';dbname='.$this->dbName, $this->dbUser, $this->dbPass);
    }
     
    public static function connect($dbDetails = array()) {
         
        // Check if instance is already exists      
        if(self::$instance == null) {
            self::$instance = new database($dbDetails);
        }
         
        return self::$instance;
         
    }
     
    private function __clone() {
        // Stopping Clonning of Object
    }
     
    private function __wakeup() {
        // Stopping unserialize of object
    }
     
}

نشانه هایی وجود دارد که  می گوید کلاس بالا یک کلاس Singleton است. اولین مورد متد سازنده کلاس می باشد که از نوع private می باشد که جلوی ایجاد کلاس توسط دستور new را می گیرد. نشانه دیگر وجود یک متغیر static می باشد که نمونه ایجاد شده جاری را در خود نگه می دارد.

$dbDetails = array(
        'db_name' => 'designpatterns',
        'db_host' => 'localhost',
        'db_user' => 'root',
        'db_pass' => 'mysqldba'
);
 
$db1 = database::connect($dbDetails);
var_dump($db1);
$db2 = database::connect($dbDetails);
var_dump($db2);
$db3 = database::connect($dbDetails);
var_dump($db3);
$db4 = database::connect($dbDetails);
var_dump($db4);
 
// Output
 
object(database)[1]
  private 'dbName' => string 'designpatterns' (length=14)
  private 'dbHost' => string 'localhost' (length=9)
  private 'dbPass' => string 'mysqldba' (length=8)
  private 'dbUser' => string 'root' (length=4)
  public 'dbh' => object(PDO)[2]
object(database)[1]
  private 'dbName' => string 'designpatterns' (length=14)
  private 'dbHost' => string 'localhost' (length=9)
  private 'dbPass' => string 'mysqldba' (length=8)
  private 'dbUser' => string 'root' (length=4)
  public 'dbh' => object(PDO)[2]
object(database)[1]
  private 'dbName' => string 'designpatterns' (length=14)
  private 'dbHost' => string 'localhost' (length=9)
  private 'dbPass' => string 'mysqldba' (length=8)
  private 'dbUser' => string 'root' (length=4)
  public 'dbh' => object(PDO)[2]
object(database)[1]
  private 'dbName' => string 'designpatterns' (length=14)
  private 'dbHost' => string 'localhost' (length=9)
  private 'dbPass' => string 'mysqldba' (length=8)
  private 'dbUser' => string 'root' (length=4)
  public 'dbh' => object(PDO)[2]

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

Singleton یک الگوی ضد الگو!

این الگوی طراحی برای برخی دلایل همچنین ضد الگو نیز نامیده می شود، که در زیر عنوان کرده ایم:

  1. این الگو اصل SRP را نقض می کند زیرا کلاس مسئولیت های زیادی بر عهده دارد. مسئولیت ایجاد نمونه از خودش.
  2. این الگو یک وضعیت سراسری را در اپلیکیشن معرفی می کند. می توانم بگویم که وضعیت سراسری خیلی بد است زیرا هر کدی می تواند مقادیر آن را دستکاری کند. همچنین در زمان خطایابی کار مشکلی است که پیدا کنیم کدام قسمت از کد وضعیت کنونی نمونه سراسری را ایجاد کرده است.
  3. Singleton به طور کلی ایده بدی است اگر unit testing انجام می دهید.

برای ارسال نظر لطفا وارد شوید.