Singleton Pattern - Tek Nesne Deseni

Singleton, bir sınıfın yalnızca tek bir instance'a sahip olmasını sağlarken bu instance'a genel bir erişim noktası sağlamanıza olanak tanıyan creational bir tasarım modelidir.

Daha önceki Tasarım Deseni / Kalıbı / Örüntüsü (Design Pattern) Nedir? başlıklı yazımızda tanıttığımız Creational Patterns altında yer alan Singleton Pattern Nedir sorusunu ve kod örneklerini bu yazımızda oldukça detaylı paylaşıyoruz.


Singleton modeli, Single Responsibility Principle'ı ihlal ederek aynı anda iki sorunu çözer:

1- Bir sınıfın yalnızca tek bir instance'ı olduğundan emin olur. Neden biri bir sınıfın kaç instance'ı olduğunu kontrol etmek istesin ki? Bunun en yaygın nedeni, örneğin bir veritabanı veya dosya gibi bazı paylaşılan kaynaklara erişimi kontrol etmektir.

Nasıl çalıştığına bakalım: Bir nesne yarattığınızı, ancak bir süre sonra yeni bir tane yaratmaya karar verdiğinizi hayal edin. Yeni bir nesne yaratmak yerine, önceden oluşturduğunuz nesneyi alabilirsiniz.

Bir constructor çağrısının tasarım gereği her zaman yeni bir nesne döndürmesi gerektiğinden, bu davranışın normal bir constructor ile implementasyonunun imkansız olduğunu unutmayın.

Müşteriler, her zaman aynı nesneyle çalıştıklarını bile fark etmeyebilirler.

2- Bu instance'a genel bir erişim noktası sağlayın. Bazı temel nesneleri saklamak için kullandığınız global değişkenleri hatırlıyor musunuz? Çok kullanışlı olmalarına rağmen, herhangi bir kod potansiyel olarak bu değişkenlerin içeriğinin üzerine yazabileceği ve uygulamayı çökertebileceği için çok güvensizdirler.

Global bir değişken gibi, Singleton deseni de programdaki herhangi bir yerden bazı nesnelere erişmenizi sağlar. Ancak, bu instance'ın üzerine başka bir kod tarafından yazılmasını da önler.

Bu sorunun başka bir yanı daha var: 1. sorunu çözen kodun programınızın her yerine dağılmasını istemiyorsunuz. Özellikle kodunuzun geri kalanı zaten buna bağlıysa, bir sınıfta olması çok daha iyidir.

Günümüzde, Singleton deseni o kadar popüler hale geldi ki, listelenen sorunlardan sadece birini çözse bile insanlar bir şeye singleton diyebilir.

✅ Çözüm

Singleton'ın tüm implementasyonları şu iki adıma sahiptir:

  • Diğer nesnelerin Singleton sınıfıyla new operatörü kullanmasını önlemek için varsayılan constructor'ı private yapın.
  • Constructor görevi gören statik bir oluşturma fonksiyonu oluşturun. Bu yöntem private constructor'ı bir nesne oluşturmaya çağırır ve onu statik bir fied'a kaydeder. Bu yönteme yapılan aşağıdaki tüm çağrılar, önbelleğe alınmış nesneyi döndürür.

Kodunuzun Singleton sınıfına erişimi varsa, Singleton'ın statik yöntemini çağırabilir. Bu nedenle, bu yöntem her çağrıldığında, her zaman aynı nesne döndürülür.

🚗 Gerçek Dünya Analoji

Hükümet, Singleton modelinin mükemmel bir örneğidir. Bir ülkenin yalnızca bir resmi hükümeti olabilir. Hükümetleri oluşturan bireylerin kişisel kimliklerinden bağımsız olarak, “X Hükümeti” unvanı, sorumlu insan grubunu tanımlayan küresel bir erişim noktasıdır.

⚓ Structure

Structure
  1. Singleton sınıfı, kendi sınıfının aynı instance'ını döndüren getInstance statik yöntemini declare eder. Singleton'ın constructor'ı, istemci kodundan gizlenmelidir. Singleton nesnesini almanın tek yolu getInstance yöntemini çağırmak olmalıdır.

# Singleton Pseudocode

Bu örnekte, veritabanı bağlantı sınıfı bir Singleton gibi davranır. Bu sınıfın genel bir constructor'ı yoktur, bu nedenle nesnesini almanın tek yolu getInstance yöntemini çağırmaktır. Bu yöntem, ilk oluşturulan nesneyi önbelleğe alır ve sonraki tüm çağrılarda onu döndürür.

// Veritabanı sınıfı, istemcilerin program boyunca aynı veritabanı 
// bağlantısı örneğine erişmesini sağlayan "getInstance" yöntemini tanımlar.
class Database is
    // Singleton instance'ını depolama alanı statik olarak bildirilmelidir.
    private static field instance: Database

    // Singleton constructor, 'new' operatörle doğrudan construction
    // çağrılarını önlemek için her zaman private olmalıdır.
    private constructor Database() is
        // Bir veritabanı sunucusuna gerçek bağlantı 
        // gibi bazı initialization kodları.
        // ...

    // Singleton instance'ına erişimi kontrol eden statik yöntem.
    public static method getInstance() is
        if (Database.instance == null) then
            acquireThreadLock() and then
                // Bu, erişim engelinin açılmasını beklerken instance'ın başka
                // bir iş parçacığı tarafından başlatılmadığından emin olur.
                if (Database.instance == null) then
                    Database.instance = new Database()
        return Database.instance

    // Son olarak, herhangi bir singleton, instance'da yürütülebilecek 
    // bazı business logic tanımlamalıdır.
    public method query(sql) is
        // Örneğin, bir uygulamanın tüm veritabanı sorguları bu yöntemden geçer. 
        // Bu nedenle, kısma veya önbelleğe alma mantığını buraya yerleştirebilirsiniz.
        // ...

class Application is
    method main() is
        Database foo = Database.getInstance()
        foo.query("SELECT ...")
        // ...
        Database bar = Database.getInstance()
        bar.query("SELECT ...")
        // "bar" değişkeni, 
        // "foo" değişkeniyle aynı nesneyi içerecektir.

💡 Uygulanabilirlik

🐛 Programınızdaki bir sınıfın tüm istemciler için yalnızca tek bir instance'ı olması gerektiğinde Singleton desenini kullanın; örneğin, programın farklı bölümleri tarafından paylaşılan tek bir veritabanı nesnesi.

⚡ Singleton modeli, private oluşturma yöntemi dışında bir sınıfın nesneleri yaratmanın diğer tüm yollarını devre dışı bırakır. Bu yöntem ya yeni bir nesne oluşturur ya da daha önce oluşturulmuşsa var olan bir nesneyi döndürür.

🐛 Global değişkenler üzerinde daha sıkı denetime ihtiyacınız olduğunda Singleton modelini kullanın.

⚡ Global değişkenlerden farklı olarak Singleton modeli, bir sınıfın yalnızca bir instance'ının olduğunu garanti eder. Singleton sınıfının kendisi dışında hiçbir şey önbelleğe alınmış instance'ın yerini alamaz. Bu sınırlamayı her zaman ayarlayabileceğinizi ve istediğiniz sayıda Singleton instance'ının oluşturulmasına izin verebileceğinizi unutmayın. Değiştirilmesi gereken tek kod parçası, getInstance yönteminin gövdesidir.

🗎 Nasıl Implemente Edeceğiz?

  1. Singleton instance'ını depolamak için sınıfa private bir statik field ekleyin.
  2. Singleton instance'ını almak için genel bir statik oluşturma yöntemi bildirin.
  3. Statik yöntem içinde "lazy initialization" uygulayın. İlk çağrısında yeni bir nesne oluşturmalı ve onu statik alana koymalıdır. Yöntem, sonraki tüm çağrılarda her zaman bu instance'ı döndürmelidir.
  4. Sınıfın constructor'ını private yapın. Sınıfın statik yöntemi yine de constructor'ını çağırabilir, ancak diğer nesneler çağıramaz.
  5. İstemci kodunu gözden geçirin ve singleton'un constructor'ına yapılan tüm doğrudan çağrıları, statik oluşturma yöntemine yapılan çağrılarla değiştirin.

⚖️ Singleton Avantajları ve Dezavantajları

✔️ Bir sınıfın yalnızca tek bir instance'ı olduğundan emin olabilirsiniz.

✔️ Bu instance'a genel bir erişim noktası kazanırsınız.

✔️ Singleton nesnesi yalnızca ilk kez istendiğinde initialize edilir.

❌ Single Responsibility Principle'ı ihlal eder. Desen aynı anda iki sorunu çözer.

❌ Singleton deseni, örneğin, programın bileşenleri birbirleri hakkında çok fazla şey bildiğinde, kötü tasarımı maskeleyebilir.

❌ Desen, multithreaded bir ortamda özel işlem gerektirir, böylece birden çok iş parçacığı, birkaç kez tek bir nesne oluşturmaz.

❌ Singleton'ın istemci kodunu test etmek zor olabilir, çünkü birçok test freamewroku, sahte nesneler üretirken kalıtımı kullanır. Singleton sınıfının constructor'ı private olduğundan ve çoğu dilde statik yöntemleri geçersiz kılmak imkansız olduğundan, singleton ile alay etmek için yaratıcı bir yol düşünmeniz gerekecektir. Ya da sadece testleri yazmayın. Veya Singleton desenini kullanmayın.

⇄ Diğer Desenlerle İlişkisi

  • Çoğu durumda tek bir Facade nesnesi yeterli olduğundan, bir Facade sınıfı genellikle bir Singleton'a dönüştürülebilir.
  • Bir şekilde nesnelerin tüm paylaşılan durumlarını yalnızca bir Flyweight nesnesine indirgemeyi başarmış olsaydınız, Flyweight Singleton'a benzerdi. Ancak bu kalıplar arasında iki temel fark vardır:
  1. Yalnızca bir Singleton instance'ı olmalıdır, oysa bir Flyweight sınıfı, farklı içsel durumlara sahip birden çok instance'a sahip olabilir.
  2. Singleton nesnesi değiştirilebilir olabilir. Flyweight nesneleri immutable'dır.

<> Kod Örneği

Aşağıdaki public repo adresine giderek adresine giderek örnek Singleton kodlarını inceleyebilirsiniz.

Github repoya gitmek için Tıklayınız.