پارسا: میخوام از کلاس یه آبجکت بسازم...
پوریا: خُب با new بساز!
پارسا: نمیخوام هربار ازش instance جدید بسازم، فقط یه instance میخوام! فرض کن با هربار نمونهسازی از کلاس، اتفاق بدی برام میافته! ترجیح میدم فقط و فقط یه بار این اتفاق بد بیفته تا اینکه تکرار بشه!
Ensures a class only has one instance, and provides a global point of access to it.
سینگلتون شما را مطمئن میسازد که یک کلاس تنها یک نمونه دارد و یک نقطهی دسترسی سراسری برای آن ارائه میدهد.
به کد زیر دقت کنید:
$object1 = new Cache(); $object2 = new Cache(); var_dump($object1 === $object2);
خروجی false است؛ زیرا هر کدام از این دو آبجکت، نمونهی جداگانهای از کلاس Cache هستند. کاری که سینگلتون انجام میدهد محدودکردن نمونهسازی است تا جواب این شرط true شود. کلمهی Singleton یعنی تَک، پس نمونهای که ساخته میشود بینظیر است و مطمئن هستیم که از آن کلاس، بیشتر از یک نمونه وجود ندارد.
چرا باید نمونهسازی دوباره از یک کلاس را محدود کنیم؟ جواب این پرسش را خودمان بر اساس نیاز میفهمیم، زیرا استفاده از دیزاینپترنها بر اساس نیاز است. ما آنها را در هر جای پروژه به کار نمیبریم و تا مشکل x نباشد، دست به دامن دیزاینپترنی که برای حل آن آمده نمیشویم. در کتاب Head First Design Patterns از کلاسهای مربوط به کش (Cache) و پیکربندی سیستم اسم برده شده که در صورت نیاز یک نمونه از آنها ساخته میشود و در ادامهی روند برنامه از همان نمونه استفاده میشود. توجه کنید که ساخت نمونه از یک کلاس سینگلتون در هنگام نیاز اتفاق میافتد، پس اگر کاربر درخواستی برای سیستم بفرستد که برای اجرای آن نیازی به آن کلاس سینگلتون نباشد، هیچ نمونهای ساخته نمیشود. اما اگر از یک متغیر سراسری (Global) در ابتدای بارگزاری سیستم استفاده میکردیم، ساخت نمونه در هر صورتی اتفاق میافتاد.
سینگلتون سه ویژگی کلیدی دارد:
تلاش کنیم که با استفاده از آموختههایی که از زبان برنامهنویسی خود داریم، این سه خصوصیت را پیاده کنیم: برای پیادهسازی خصوصیت اول، سازندهی کلاس را خصوصی (Private) تعریف میکنیم و برای خصوصیت دوم یک متد Static میسازیم و خصوصیت سوم را به وسیلهی یک شرط مهیا میکنیم.
class Singleton { private static $instance; private function __construct() {} public static function getInstance() { if (! static::$instance) { static::$instance = new static; } return static::$instance; } }
از آنجا که construct__ را به صورت خصوصی تعریف کردهایم، دیگر امکان نمونهسازی با new وجود ندارد، پس متد getInstance به عنوان نقطهی دسترسی تعریف شده است و اگر نمونه هنوز وجود نداشته باشد، آن را میسازد و بعد نمونه را برمیگرداند:
$obj1 = Singleton::getInstance(); $obj2 = Singleton::getInstance(); var_dump($obj1 === $obj2);
این بار خروجی true است و هردو نمونه یکی هستند، اما یک جای کار میلنگد:
$obj1 = Singleton::getInstance(); $obj2 = clone $obj1; var_dump($obj1 === $obj2);
هرچند که راه نمونهسازی با کلمهی کلیدی new را بستهایم ولی هنوز امکان کپیکردن یک نمونه با clone وجود دارد! برای حل این مشکل باید متد clone__ را هم به صورت private بنویسیم:
private function __clone() {}
یک بار دیگر امتحان کنیم:
$obj1 = Singleton::getInstance(); $obj2 = unserialize(serialize($obj1)); var_dump($obj1 === $obj2);
دوباره نمونهی دومی ایجاد کردیم و این سینگلتون را نقض میکند، اما اگر متد serialize__ یا unserialize__ را هم private کنیم، از اجرای کد بالا جلوگیری کردهایم و به سینگلتون رسیدهایم.
با لاراول میتوان منطقی که گفته شد را به صورت دیگری داشت، کافی است که در متد register که درService Providerها وجود دارد، کلاس خود را bind کنید:
$this->app->singleton('currency.service', function () { return new Currency(); });
از این به بعد میتوانیم با کلید currency.service به کلاس Currency دسترسی داشته باشیم:
$obj1 = app()->make('currency.service'); $obj2 = app()->make('currency.service'); var_dump($obj1 === $obj2);
خروجی true است و هردو آبجکت، واقعاً یک نمونه هستند!
با ()this->app$ به کلاس Application دسترسی پیدا کردهایم که از کلاس Container ارثبری کرده است. کلاس Container هم متدی به نام singleton دارد که از همان کلاس bind استفاده میکند، اما با یک تفاوت کوچک:
public function singleton($abstract, $concrete = null) { $this->bind($abstract, $concrete, true); }
آرگومان سوم، مقدار true را به پارامتری به نام shared میدهد که یگانهبودن آنچه bind شده است را تعیین میکند.
منابعی که این مقاله را شکل دادند:
شاید این نوشتهها هم برایتان جالب باشند: