3 دقیقه میانگین مدت زمان مطالعه است

یکی از دیزاین پترن هایی که کاربرد زیادی در برنامه نویسی داره، دیزاین پترن Singleton هستش.
این پترن زمان هایی کاربرد داره که ما فقط به یه Instance از کلاس داریم.

این پترن در حالت های مختلفی امکان استفاده داره:

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

شکل کلی این پترن در زمان هایی که برنامه multiThread هست، بصورت زیر هستش:

namespace DesignPatterTest
{
    public class SingletonPattenClass
    {
        private static SingletonPattenClass _instance;
        private static readonly object Lock = new object();

        private SingletonPattenClass()
        {
        }

        public static SingletonPattenClass Instance()
        {
            if (_instance == null)
            {
                lock (Lock)
                {
                    if (_instance == null)
                    {
                        _instance = new SingletonPattenClass();
                    }
                }
            }

            return _instance;
        }
    }
}

روش کلی به این صورت هست که ما Constructor رو به حالت Private درمیاریم و وظیفه ساخت رو به یه متد Static با اسم دلخواه Instance میدیم.

دلیل چک کردن نال نبودن اول برای جلوگیری از سربار lock هستش و چک نال بودن دوم زمانی کاربرد داره که بطور مثال دو ترد همزمان، شرط اول رو رد میکنن و یکی از اونها میتونه وارد lock بشه و یه instance جدید از کلاس بسازه، وقتی این ترد از قسمت lock خارج میشه ترد دوم وارد بخش lock میشه.
در این زمان اگه ما چک نال بودن دوم رو نداشته باشیم یه instance جدید هم ساخته میشه.
پس برای جلوگیری از این اتفاق، دو بار شرط نال نبودن رو نوشتیم.
درباره این مشکل میتونید در لینک زیر مطالعه کنید:

Double_checked_locking

روش فراخونی متودهای این کلاس هم بصورت زیر هست:

SingletonPattenClass.Instance().MyMethod();

اما روش بالا دارای محدودیت هایی هستش:

  • به دلیل استفاده از فیلدهای static قابلیت تست پذیری رو خیلی کم میکنه
  • قابلیت ریفکتور و تغییر در آینده رو کم میکنه و استفاده کننده ها وابسته به نوع پیاده سازی کلاس ما هستن
  • امکان تزریق وابستگی ها به این کلاس یه مقدار سخت میشه

برای برطرف کردن مشکلات بالا روش جدید ارائه شده که وظیفه ساخت کلاس رو به IOC میدیم.
بطور مثال در .net core برای این کار کافیه بصورت زیر در فایل Startup عمل کنید:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<ISingleton, Singleton>();
}

برای ساخت یک وهله از کلاس میشه اون کلاس رو بصورت Static درآورد، ولی این روش محدودیت هایی در مقابل با کلاس Singleton داره:

  • ما نمی تونیم نمونه ای از یک کلاس استاتیک رو ایجاد کنیم. اما می تونیم یک نمونه واحد از کلاس singleton ایجاد کنیم و از اون نمونه singleton دوباره استفاده کنیم.
  • وقتی کامپایلر کلاس استاتیک رو کامپایل می کند ، در داخل کلاس استاتیک را به عنوان یک کلاس انتزاعی و مهر و موم شده در نظر می گیرد. به همین دلیل هست که ما نه یک نمونه ایجاد می کنیم و نه یک کلاس ثابت را گسترش می دهیم.
  • هنگامی که برنامه یا فضای نامی که حاوی کلاس Singleton است بارگذاری می شود ، یک کلاس Singleton می تونه بصورت Lazy مقداردهی اولیه بشه یا به طور خودکار توسط CLR بارگیری بشه. در حالی که یک کلاس استاتیک به طور کلی وقتی برای اولین بار بارگیری می شود مقداردهی اولیه می شهعبور از کلاس استاتیک به عنوان یک پارامتر متد امکان پذیر نیست در حالی که ما می توانیم نمونه singleton را به عنوان یک پارامتر متد در C # منتقل کنیم.
  • کلاس Singleton قابلیت ارث بری از کلاس های دیگه رو داره ولی اینها با کلاس استاتیک امکان پذیر نیست. بنابراین کلاس Singleton در مقایسه با کلاسهای استاتیک انعطاف پذیرتر هستش.
  • ما می تونیم شی کلاس Singleton رو Clone کنیم در حالی که Clone کلاس استاتیک امکان پذیر نیست.
  • ما نمی تونیم الگوی طراحی Dependency Injection رو با استفاده از کلاس Static پیاده سازی کنیم چون کلاس استاتیک واسط نیست.
  • Singleton به معنای یک شی single واحد در طول چرخه حیات برنامه هست ، بنابراین دامنه دسترسی در سطح برنامه است. ولی کلاس استاتیک هیچ اشاره گر Object نداره ، بنابراین دامنه دسترسی در سطح Domain App هست.