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

چند نکته در پیاده سازی این الگو وجود دارد

  • سازنده کلاس ( Constructor ) باید private باشد. بنابراین شما نمیتواند به طور مستقیم یک نمونه از این کلاس را از طریق new کردن بسازید.
  • قبل از ساخت یک نمونه از شی, ابتدا باید بررسی کنید که آیا نمونه ای از این شی وجود دارد یا نه. اگر وجود داشت همان شی را برمیگردانید در غیر این صورت یک نمونه از آن را خواهید ساخت.

ویژگی های Singleton

  • شما زمانی یک نمونه از شی را میسازید که عضوی از کلاس ارجاع شده باشد.
  • کلاس singleton یک پراپرتی از نوع public و static دارد که باعث میشود به صورت عمومی به آن دسترسی داشته باشند. این پراپرتی اطمینان می دهد که روند نمونه سازی از کلاس تا زمانی که پراپرتی Instance فراخوانی نشده باشد صورت نمیگیرد.
  • این کلاس از نوع sealed است و از ارث بری سایر کلاس ها از این کلاس جلوگیری میکند. ( هیج کلاسی نمیتواند از این کلاس ارث بری کند اگر قبل از نام کلاس کلمه کلیدی sealed آمده باشد. )
  • سازنده این کلاس خصوصی ( private ) است. بنابراین شما نمیتواند مستقیما یک نمونه از این کلاس را بسازید. این کمک میکند که فقط یک نمونه از شی در سیستم وجود داشته باشد.
public sealed class Singleton
{
    private static Singleton _instance;
    //Private constructor is used to prevent creation of instances with 'new' keyword outside this class
    private Singleton()
    {

    }
    public static Singleton Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new Singleton();
            }
            return _instance;
        }
    }
}

این رویکرد بر روی سیستم های تک نخی ( single-threaded )  به خوبی اجرا میشود. اما اگر در سیستم های چند نخی اجرا شود ممکن است چندین نمونه از کلاس ساخته شود که singleton را نقض میکند. به عنوان مثال در سیستم های چند نخی ممکن است دو یا چند نخ ( thread ) به طور همزمان کد زیر را فراخوانی کنند.

if (_instance == null)

و همین امر باعث میشود چند نمونه از شی به طور همزمان ایجاد میشود !

برای جلوگیری از این مشکل چندین راه وجود دارد و هرکدام مزایا و معایب خود را دارند. در این قسمت از روش double checked locking استفاده خواهیم کرد.

public sealed class Singleton
    {
        /// <summary>
        /// We are using volatile to ensure that assignment to the singletone variable finishes before it’s access.
        /// </summary>
        private static volatile Singleton _instance = new Singleton();
        private static object lockObject = new object();
        private Singleton()
        {
        }
        public static Singleton Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (lockObject)
                    {
                        if (_instance == null)
                        {
                            _instance = new Singleton();
                        }
                    }
                }
                return _instance;
            }
        }
    }

volatile : در مثال بالا در هنگام تعریف نمونه کلاس از کلمه کلیدی volatile استفاده کردیم.

 volatile امکان پیاده سازی مکانیزم سریال ( پشت سرهم ) را فراهم میکند. کلمه کلیدی volatile قابل استفاده برای فیلدهای کلاس یا struct ها می باشد. نمیتواند از آن برای متغییر های محلی استفاده کنید.

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

lock : کلمه کلیدی lock اطمینان میدهد که یک نخ ( thread ) وارد یک نقطه بحرانی از کد نشود تا زمانی که یک نخ ( thread ) دیگر در آن منطقه بحرانی باشد. اگر نخ ( thread ) دیگری سعی کند که وارد نقطه بحرانی شود که یک نخ ( thread ) دیگر فعلا در آن است باید صبر کند و یا block شود تا زمانی که نخ ( thread ) از منطقه بحرانی خارج شود.

و در نهایت در کد بالا فیلد instance از نوع volatile ساخته شد که به صورت سریال ( پشت سرهم ) میتوان به آن دسترسی داشت و از کلمه کلیدی lock برای جلوگیری همزمان نخ ها برای قطعه کد مورد نظر استفاده شده است و این روش double checked locking نام دارد.

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

class Program
{
    static void Main(string[] args)
    {
        Singleton singleton = Singleton.Instance;
        Console.ReadKey();
    }
}

:)