Singleton در Kotlin

Singleton یکی از محبوب ترین هاست. همه او را می شناسند، همه درباره او صحبت می کنند، و همه می دانند کجا باید او را جستجو کنند.
حتی افرادی که دوست ندارند از الگوهای طراحی (Design patterns) استفاده کنند، Singleton را به نام می شناسند. در یک مقطع، حتی به عنوان یک ضد الگو (anti-pattern) معرفی شد، اما این موضوع تنها به دلیل محبوبیت گسترده اش بود.
بنابراین، خطاب به کسانی که برای اولین بار با آن مواجه می شوند، این الگوی طراحی چیست؟
معمولاً اگر کلاسی پیاده سازی کرده اید، می توانید هر تعداد نمونه که بخواهید از آن بسازید. به عنوان مثال، فرض کنید از هر دوی ما خواسته شده که همه فیلم های مورد علاقه خود را فهرست کنیم:

توجه داشته باشید که می توانیم هر تعداد نمونه که بخواهیم از List را بسازیم و هیچ مشکلی از این بابت وجود ندارد. اکثر کلاس ها می توانند چندین نمونه داشته باشند.
در ادامه، اگر هر بخواهیم بهترین سری Quick and Angry را فهرست کنیم، چطور؟

در نظر داشته باشید که این دو لیست دقیقاً یکسان هستند زیرا خالی اند. آنها همچنان خالی خواهند ماند زیرا تغییر ناپذیر (immutable) هستند.

از آنجایی که این دو نمونه از یک کلاس دقیقاً مشابه هستند، طبق equals method ، نگه داشتن آنها برای چندین بار در حافظه منطقی نیست. اگر همه ارجاعات به یک لیست خالی به یک نمونه از یک شی اشاره کنند عالی خواهد بود. در واقع، اگر در موردش کمی فکر کنید، این همان چیزی است که با null اتفاق می افتد. همه null ها یکسان هستند.

این ایده اصلی پشت الگوی طراحی Singleton است.
چند مورد برای الگوی طراحی Singleton وجود دارد:
– ما باید دقیقاً یک نمونه (instance) در سیستم خود داشته باشیم.
– این نمونه باید از هر بخشی از سیستم ما قابل دسترسی باشد.

در جاوا و برخی از زبان های دیگر، این کار بسیار پیچیده است. ابتدا باید از ایجاد نمونه های جدید یک شی (object) با ساخت سازنده (constructor) برای کلاس خصوصی (private class) جلوگیری کنید.
سپس، همچنین باید مطمئن شوید که نمونه سازی (instantiation) ترجیحاً lazy و thread-safe و کارآمد است، با شرایط زیر:

  • Lazy : ممکن است وقتی برنامه ما شروع می‌شود، نخواهیم یک شی singleton را نمونه‌سازی کنیم، زیرا ممکن است یک عملیات پر هزینه باشد. ما می خواهیم آن را تنها زمانی که برای اولین بار مورد نیاز است نمونه سازی کنیم.
  • Thread-safe : اگر دو thread سعی می‌کنند یک شی singleton را در یک زمان نمونه‌سازی کنند، هر دو باید یک نمونه را دریافت کنند نه دو نمونه متفاوت.
  • Performant : اگر بسیاری از thread ها به طور همزمان سعی می کنند یک شی singleton را نمونه‌سازی کنند، ما نباید آنها را برای مدت طولانی مسدود کنیم، زیرا این کار اجرای آنها را متوقف می کند.

برآورده کردن همه این الزامات در جاوا یا C++ بسیار دشوار است، یا حداقل بسیار طولانی.

Kotlin با معرفی کلمه کلیدی به نام object، ایجاد singleton را آسان می‌کند. ممکن است این کلمه کلیدی را از زبان Scala به یاد بیاورید. با استفاده از این کلمه کلیدی، ما یک پیاده سازی از یک شی singleton را دریافت خواهیم کرد که تمام نیازهای ما را برآورده می کند.

 نکته مهم :‌ کلمه کلیدی object برای چیزی بیشتر از ایجاد singleton استفاده می شود.

ما اشیاء را دقیقاً مانند یک کلاس معمولی اما بدون سازنده declare می کنیم، زیرا یک شی singleton نمی تواند توسط ما نمونه سازی شود:

از این به بعد، ما می‌توانیم از هر کجای کدمان به NoMoviesList دسترسی داشته باشیم و دقیقاً یک نمونه از آن وجود خواهد داشت:

به علامت تساوی ارجاعی (referential equality) توجه کنید که بررسی می کند دو متغیر به یک شی در حافظه اشاره می کنند. آیا این واقعاً یک لیست است؟
بیایید تابعی ایجاد کنیم که لیست فیلم های ما را چاپ کند:

وقتی لیست اولیه فیلم ها را ارسال می کنیم، کد به خوبی compile می شود:

اما اگر لیست خالی فیلم خود را ارسال کنیم، کد compile نمی شود:

دلیل این امر این است که تابع ما فقط آرگومان هایی از نوع لیست رشته ها است را می پذیرد، در حالی که هیچ چیزی نمی تواند به تابع بگوید که NoMoviesList از این نوع است (حتی اگر نامش آن را نشان دهد).
خوشبختانه، در Kotlin، اشیاء singleton می توانند اینترفیس ها را پیاده سازی کنند و یک generic List
interface در دسترس است :

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

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

اگر کنجکاو هستید، این تابع یک شی singleton را برمی گرداند که یک List را پیاده سازی می کند. می‌توانید پیاده‌سازی کامل را در کد منبع Kotlin با استفاده از IntelliJ IDEA یا در GitHub (https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/kotlin/collections/Collections.kt) مشاهده کنید. این یک مثال عالی از نحوه استفاده فعالانه الگوهای طراحی در نرم افزارهای مدرن است.

object یک تفاوت عمده با یک کلاس دارد : نمی تواند سازنده داشته باشد. اگر نیاز به اجرای مقداردهی اولیه برای Singleton خود دارید، مانند بارگیری داده ها از یک فایل پیکربندی برای اولین بار، می توانید به جای آن از بلوک init استفاده کنید:

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