Singleton Design Pattern

SingletonSingleton tasarım deseni yazılım mühendisliğinin en çok bilinen tasarım desenlerinden birisidir. Temel olarak basit bir singleton sınıf tasarlandığında kendisinden sadece bir instance oluşturmamıza izin verir ve her instance çağırımında aynı instance kullanılmış olur. Genel olarak singleton sınıflardan bir instance oluşturulurken herhangi bir parametre kullanılarak instance oluşturulmaz aksi halde oluşturulan singleton instance’ın ikinci bir çağırımında farklı bir parametre kullanımı farklı problemlere neden olur! (Eğer aynı instance aynı parametreler ile farklı çağırımlarda kullanılmak isteniyorsa factory pattern daha uygun bir kullanım olacaktır.) Ayrıca singleton sınıfları lazy olarak oluşturmak singleton desenin tipik bir gereksinimidir.



Lazy = Nesne kullanılmadığı sürece oluşturulmaz anlamına gelmektedir.

C# içerisinde singleton tasarım desenini gerçekleştirmenin birbirinden farklı çeşitli yolları vardır. Bu farklı teknikler genel olarak thread-safe olan, thread-safe olmayan, lazy yüklenen, basit ve yüksek performanslı olan singleton olarak kullanılabilmektedir.

Tüm bu teknikleri gerçekleştirirken 4 ortak karakteristik özellik uygulanmaktadır :

  • Parametresiz private tek bir (constructor) yükleyici oluşturulur. Bu diğer sınıfların instance oluşturmasını engellemek içindir. Ayrıca bu şekilde alt sınıf (subclass) oluşturulmasıda engellenmiş olur.
  • Singleton sınıf sealed olmalıdır. Diğer sınıflar tarafından türetilme yapılmaması için. Bu çok gerekli bir adım değildir ancak yazılım geliştirmede sıkı bir politika izliyorsanız sealed yapmak JIT’in bazı durumlarda daha iyi optimizasyon yapmasına yardımcı olur.
  • Eğer static değişken kullanarak singleton yapacaksanız, singleton sınıfın instance’ını tutacak static bir değişken oluşturulur.
  • Singeton referansını döndürecek public static bir özellik oluşturulur.

Tüm tekniklerde bulunabilen singleton referansı tutan public static özellik thread-safety yada performansa herhangi bir etkisi olmadan kolay bir biçimde metodada dönüştürülebilir.

1. Teknik Thread-Safe Olmayan Singleton

// Kötü bir kullanımdır kullanmayınız.
public sealed class Singleton
{
    private static Singleton instance=null;

    private Singleton()
    {
    }

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

Dikkat edileceği üzere yukarıdaki gibi bir kullanım thread-safe değildir. İki farklı thread aynı anda if (instance==null) şartını çalıştırdığında true değerini alacak ve iki instance oluşturacaktır buda tasarım deseni kurallarını ihlal etmiş olacaktır. Belkide daha önceden bir thread tarafından if (instance==null) ifadesi çalıştırılıp instance oluşturulmuş olabilir, fakat bellek modeli yeni oluşturulan instance değerinin diğer thread’ler tarafından görüntülebileceğinin garantisini vermediğinden (bellek bariyerleri kullanmadığınız sürece) diğer thread’ler bu instance’a erişemeyebilir buda size ayrı bir problem oluşturacaktır.

2. Teknik Basit Thread-Safe Singleton

public sealed class Singleton
{
    private static Singleton instance = null;
    private static readonly object lockObj = new object();

    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            lock (lockObj)
            {
                if (instance == null)
                {
                    instance = new Singleton();
                }
                return instance;
            }
        }
    }
}

Yukarıdaki tasarıma dikkat edileceği üzere thread-safe olduğu gözlemlenmektedir. Herhangi bir thread paylaşılan nesne ile işi bitene kadar nesneyi kilitlemektedir ve her thread nesnenin instance’ının oluşturulup oluşturulmadığını her defasında kontrol etmektedir. Bu durum arka planda bellek bariyerlerini ilgilendirir ve bu durumda sadece bir thread’in tek instance oluşturduğundan emin olunduğu durumdur. Ne yazık ki her instance çağırma durumunda kilitleme (lock) işlemi gerçekleşeceğinden yüksek ölçüde performans kaybı gözlemlenir.

3. Teknik Double-Check Kullanarak Thread-Safe Singleton

// Kötü bir kullanımdır kullanmayınız.
public sealed class Singleton
{
    private static Singleton instance = null;
    private static readonly object lockObj = new object();

    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            if (instance == null)
            {
                lock (lockObj)
                {
                    if (instance == null)
                    {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
}

Bu şekilde bir singleton uygulanması, her defasında gereksiz kilitleme yapılmasını önleyerek thread-safe olacaktır. Fakat bu tekniğinde bazı dezavantajları bulunmaktadır :

Böyle bir kullanım Java bellek modeline uygun olmadığı için Java da çalışmayacaktır.
Herhangi bir bellek bariyeri kullanılmadıği için ECMA CLI özelliklerine aykırı bir kullanımdır.
Bu teknik kullanılarak yanlış bir tasarım gerçekleştirilebilir. Ancak önceki tekniğe göre performans kazancı sağlamaktadır.

4. Teknik Tam Lazy Olmayan Thread-Safe Singleton

public sealed class Singleton
{
    private static readonly Singleton instance = new Singleton();

    // Static yükleyici (constructor) C# derleyicisine
    // tanımlanan alanın direk olarak nesne kullanılmadan
    // tipinin belirlenmeyeceğini belirtir.
    static Singleton()
    {
    }

    private Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            return instance;
        }
    }
}

Lazy = Nesne kullanılmadığı sürece oluşturulmaz anlamına gelmektedir.

Görüleceği üzere bu teknik son derece basit olmasına karşın nasıl thread-safe ve lazy olmaktadır?

C# içerisindeki static yükleyiciler (constructors) sadece sınıfın bir instance’ı oluşturulduğunda yada bu sınıfın içerisindeki static bir üye referans gösterildiğinde ve her Application Domain için bir kez çalışır. Bu teknik ile oluşturulan nesne sadece ihtiyaç oluşturulduğundan bir defa oluşturulup, her çağrımda aynı nesne kullanılacağından ve ekstra herhangi bir kontrolde yapılmasına gerek olmadığından için önceki tekniklerden daha hızlı çalışmaktadır.

Sadece bu teknikle beraber bir kısayol daha uygulanabilmektedir, intance değişkenini private static readonly yerine public static readonly yaparak Singleton özelliğinden kurtulabilirsiniz. Bu şekilde tasarımın iskelet kodu daha da kısalacaktır. Bir çok yazılımcının Singleton özelliğini tercih etmesninin sebebi başka bir zamanda özelliğe ihtiyaç duyacağını düşünmesindendir, fakat JIT tarafından kodlar işlendiğinde özellik yada değişkenin public kullanılmasının performans bakımından aynı davranışı sergilediği gözlenmektedir. (Buradaki static yükleyici (constructor) instance’ın lazy olarak kullanılması isteniyorsa gereklidir.)

5. Teknik Tam Lazy Singleton

public sealed class Singleton
{
    private Singleton()
    {
    }

    public static Singleton Instance { get { return Nested.instance; } }
        
    private class Nested
    {
        // Static yükleyici (constructor) C# derleyicisine
        // tanımlanan alanın direk olarak nesne kullanılmadan
        // tipinin belirlenmeyeceğini belirtir.
        static Nested()
        {
        }

        internal static readonly Singleton instance = new Singleton();
    }
}

Yukarıda dikkat edileceği üzere iç sınıftaki static üyenin referansı Instance özelliği kullanıldığında oluşturulacaktır. Yani bu teknik tam anlamıyla lazy olmakta ve bir önceki tekniğin performanstan yana sağladığı faydaları içermektedir.

6. Teknik .NET 4 İçerisindeki Lazy<T> Tipini Kullanarak Singleton

Eğer .Net Framework 4 yada daha yeni sürümlerini kullanıyorsanız System.Lazy<T> tipini kullanarak tasarımınızı çok basit bir biçimde lazy yapabilirsiniz. Bunun için tek yapmanız gereken bir delegeye Singleton sınıfınızın yükleyicisini (constructor) parametre olarak geçmektir, bu işlem en basit şekilde lambda ifadeleri ile yapılabilmektedir.

public sealed class Singleton
{
    private static readonly Lazy<Singleton> lazy =
        new Lazy<Singleton>(() => new Singleton());
    
    public static Singleton Instance { get { return lazy.Value; } }

    private Singleton()
    {
    }
}

Yukarıdaki örnektede gözlemleneceği üzere çok basit bir şekilde uygulanabildiği gözlenmektedir. Ayrıca sınıfınızın Instance oluşturulup oluşturulmadığını öğrenmeye ihtiyacınız var ise Lazy tipinin IsValueCreated özelliğini kullanabilirsiniz.

Kaynak ;

Implementing Singleton in C#
C# in Depth
Singleton pattern