دیزاین پترن‌ها - Singleton

یکی از متداول‌ترین دیزاین پترن‌ها، دیزاین پترن Singleton هست که در دسته Creational pattern ها قرار میگیره و زمانی استفاده میشه که با توجه به نیاز پروژه میخوایم کلاسی داشته باشیم که:فقط یک instance ازش ساخته بشهبتونیم از هر جایی از برنامه به آن instance دسترسی (global access) داشته باشیمدر این پترن یک کلاس داریم که مسئول این هست که یک instance از خودش بسازه و مطمئن باشه که در کل برنامه فقط یک instance ساخته میشه.منطق پیاده سازی به زبان سادهبرای پیاده‌سازی این پترن این مراحل باید انجام بشه:1- یک instance از نوع کلاس رو به صورت static تعریف میکنیم.2- متد constructor رو به صورت private تعریف میکنیم تا از خارج از کلاس امکان دسترسی به این متد وجود نداشته باشه.3- یک متد public تعریف میکنیم که نمونه ساخته شده از این کلاس رو برمیگردونه.Singleton pattern - UML diagramنمونه پیاده‌سازی با کد (جاوا)Singleton classClient class همونطور که در دو شکل بالا هم مشخص هست، Client برای اینکه یک instance از کلاس Singleton داشته باشه باید متد ()getInstance رو فراخوانی کنه و با این روش پیاده‌سازی همیشه مطمئن هستیم که فقط یک instance از این کلاس وجود داره.یک قدم جلوتربرای پیاده‌سازی این پترن approachهای مختلفی وجود داره که با توجه به شرایط میتونیم ازشون استفاده کنیم:1- Eager Initialization2- Lazy Initialization3- Static Block Initialization4- Thread Safe Singleton...بسته به اینکه برناممون چه ویژگی‌هایی داره و در چه محیطی میخوایم از این پترن استفاده کنیم، میتونیم یکی از این approach‌ها رو انتخاب کنیم.1- پیاده‌سازی که در قسمت قبل اشاره شد، Eager Initialization هست که ساده‌ترین حالت برای تعریف این پترن هست. در این حالت، در زمان تعریف کلاس، یک instance هم میسازیم.2- در رویکرد Lazy Initialization یک instance زمانی ساخته میشه که درخواست شده باشه؛ اصطلاحا Initialization in the first use هست. نحوه پیاده‌‎سازیش هم به این صورت هست:Lazy Initialization 3- رویکرد بعدی، Static Block Initialization هست. این رویکرد مشابه Eager Initialization هست ولی ساخت instance در بلاکِ static اتفاق میفته و امکان Exception Handling رو فراهم میکنه.Static Block Initialization 4- در یک برنامه Single-Thread میتونیم از هر کدوم از approach‌های قبل استفاده کنیم اما اگر برناممون Multi-Thread باشه، ممکنه در استفاده از این رویکردها دچار مشکل بشیم، بنابراین میریم سراغ رویکرد Thread Safe. قبل از پرداختن به این رویکرد، گریزی بزنیم به اینکه در محیط Multi-Thread ممکنه چه مشکلی پیش بیاد؟ تصور کنید دو Thread داریم و هر دو به صورت همزمان متد ()getInstance رو فراخوانی میکنند. اگر اولین مرتبه اجرای این متد باشه یعنی هنوز هیچ instance ای از کلاس ساخته نشده باشه، این امکان وجود داره که هر دو Thread همزمان وارد if بشن. در این حالت دو instance از کلاس Singleton ساخته میشه ( و برخلاف هدف این پترن هست).برای حل این مشکل میتونیم متد ()getInstance رو به صورت synchronized تعریف کنیم. به این ترتیب وقتی یک Thread در حال اجرای متد هست، Thread دیگه‌ای نمیتونه همزمان اون رو اجرا کنه و در لحظه فقط یک Thread میتونه با این متد کار کنه. بنابراین اگر چند Thread در برنامه داشته باشیم خیالمون راحته که دوتا instance از این کلاس ساخته نمیشه. البته این روش معایبی هم داره مثل اینکه میتونه، تحت شرایطی، باعث کم شدن performance برنامه بشه.Thread Safe این روش جوابگو هست اما اگر کمی دقت کنیم میبینیم مشکل معمولا در اولین فراخوانی متد ()getInstance و احتمالا بین تعداد کمی از Threadها اتفاق میفته.این رویکرد رو میشه با روش بهتری هم پیاده‌سازی کرد. در این روش از مفهوم Double Checked Locking استفاده میکنیم و lock شدن رو فقط در زمان ساخت instance انجام میدیم. به اینصورت که بلاکی که به صورت synchronized اجرا میشه داخل یک if قرار میگیره و بعد از آن برای اطمینان از اینکه هنوز instance ای ساخته نشده یک if دیگه هم اضافه میکنیم:Double Checked Locking به عبارت دیگه، اینجا برخلاف روش قبل که کل متد ()getInstance رو به صورت synchronized کنترل میکردیم، فقط وقتی که instance ای وجود نداره به صورت synchronized یک instance میسازیم و در بقیه موارد برای Threadها محدودیتی قائل نمیشیم.سخن پایانیاین مقاله یک نقطه شروع برای مطالعه دیزاین پترن Singleton هست و هنوز مطالب زیادی برای نوشتن وجود داره. سعی می‌کنم به مرور بنویسم.