Bu yazı Design Patterns/Tasarım Desenleri nedir? başlıklı yazı dizisinin bir parçasıdır.

Bu içerik ağırlıklı olarak refactoring.guru sitesindeki içeriğin tercümesi ve derlenmesinden oluşturulmuştur.

Tüm tasarım desenleri ya da diğer adıyla tasarım kalıplarına yönelik ayrıntılı içeriklere yazının sonundaki bağlantılardan ulaşabilirsiniz.

Singleton deseninin amacı

Singleton bir nesnenin sadece bir örneğinin olduğundan emin olmak ve bu nesneye ihtiyacınız olduğunda kodunuzda her yerde aynı (ve tek örneğin) çağırılmasını sağlamak için kullanılır.

Sorun

Singleton deseni SOLID programlama prensiplerinin en başında gelen tekil sorumluluk prensibine (Ara: Single Responsibility Principle) aykırılığa neden olan iki sorunu tek seferde çözüyor:

  1. Bir sınıfın tek bir örneğinin olduğuna emin olun: Neden bir sınıfa ait birden fazla örnek var önemli olsun ki? En sık karşılaşılan sebep paylaşılan bir kaynağa – örneğin bir veritabanı veya dosyaya erişimi kontrol etmektir.

    Nasıl çalışıyor? Farzedin ki bir nesne oluşturdunuz, daha sonra bir başka yerde daha bu nesneye ihtiyacınız oldu ve tekrar oluşturdunuz. Eğer oluşturduğunuz bu nesne singleton desenindeyse yeni bir nesne yerine var olan mevcut nesne geri dönecektir.

    Bu davranış normal bir oluşturucu (constructor) ile mümkün değildir, çünkü tasarımı gereği oluşturucular her zaman yeni bir nesne döndürmek zorundadır.
İstemciler sürekli aynı nesne ile çalıştıklarını farkında bilme olmayacaktır.

2. O örneğe global bir erişim noktası sağlayın. Bazı önemli objeleri saklamak için kullandığınız global değişkenler oluyor mu? Bu yöntem her ne kadar kullanışlı olsa da güvenilir değildir çünkü herhangi bir kod bu nesnelerin üzerine yazıp uygulamanızın çökmesine neden olabilir.

Singleton deseni aynı global değişkenlerde olduğu gibi nesneye istediğiniz yerde ulaşabilmesinizi sağlar ve aynı zamanda başka bir kodun nesnenizin üzerine yazmasını da engeller.

Bu sorunun bir yönü daha var: sorun 1’i çözen kodun programın her yerine dağılmasını istemezsiniz, bu çözümün tek bir sınıf içinde olması -özellikle de kodunuzun geri kalanı zaten ona bağlıysa- çok daha iyidir.

Çözüm

Tüm singleton uyarlamaları için aşağıdaki iki adım ortaktır:

  • Varsayılan constructor’ı özel (private) yapın, böylede diğer nesneler bu singleton sınıfı ile new operatörünü kullanamazlar.
  • Constructor olarak görev yapacak statik bir oluşturma metodu yazın. Aslında bu metod arka planda özel constructor’ı çağıracak ve bunu statik bir alana kaydedecektir. Bu metoda daha sonra gelen tüm çağrılar ön belleğe alınmış bu nesneyi gönderecektir.

Kodunuz Singleton sınıfına erişebiliyorsa Singleton’un statik metoduna da erişebilir. Bu metod her çağrıldığında aynı nesne döndürülecektir.

Uygulanabilirlik

Programınızda kullandığınız bir sınıfın tüm istemcilerin kullanabileceği sadece tek bir örneğinin olması için singleton desenini kullanabilirsiniz; örneğin bir programın farkı bölümlerinde kullanılan bir veritabanı nesnesi.

Singleton deseni kendi özel oluşturma metodu dışında nesne oluşturulasını engeller. Bu özel meto daha önce oluşturulmuş bir nesne varsa onu, yoksa yeni oluşturacacağı nesneyi döndürür.

Global değişkenler üzerinde daha sıkı bir kontrole ihtiyacınız olduğunda singleton desenini kullanın.

Global değişkenlerin aksine Singleton deseni bir sınıfın her zaman tek bir örneğini olmasını garanti eder. Singleton sınıfının kendisi dışında hiç bir şey önbellekteki örneği değiştiremez.

Not: Sınırlamayı ayarlayabilir ya da tamamen ortadan kaldırıp birden fazla Singleton örneği oluşturulmasını sağlayabilirsiniz. getInstance metodunuzun içeriğini değiştirerek sınıfınızın istediğiniz davranışı göstermesini sağlayabilirsiniz.

Diğer tasarım desenleri/kalıpları ile ilişkisi

  • Facade (yapısal tasarım deseni) sınıfları çoğu zaman Singleton’a dönüştürülebilir çünkü çoğu zaman tek bir facade nesnesi yeterlidir.
  • Eğer tüm paylaşılan durumları tek bir flyweight (yapısal tasarım deseni) nesnesine indirgeyebilseydiniz Flyweight Singleton’a benzeyebilirdi. Ancak bu tasarım desenleri arasında iki fark vardır;
    • Singleton’ların yalnızca tek bir örneği olmalıdır. Oysa bir flyweight sınıfı farklı içsel durumlara sahip birden çok örneğe sahip olabilir.
    • Singleton objeleri değiştirilebilir (mutable) olabilir fakat flyweight nesneleri değiştirilebilir değillerdir. (immutable)
  • Abstract Factory, Builder ve Prototype desenleri Singleton olarak uyarlanabilirler.

Singleton Kod Örnekleri

Örnek PHP Kodu

<?php

namespace RefactoringGuru\Singleton\Conceptual;

/**
 * The Singleton class defines the `GetInstance` method that serves as an
 * alternative to constructor and lets clients access the same instance of this
 * class over and over.
 */
class Singleton
{
    /**
     * The Singleton's instance is stored in a static field. This field is an
     * array, because we'll allow our Singleton to have subclasses. Each item in
     * this array will be an instance of a specific Singleton's subclass. You'll
     * see how this works in a moment.
     */
    private static $instances = [];

    /**
     * The Singleton's constructor should always be private to prevent direct
     * construction calls with the `new` operator.
     */
    protected function __construct() { }

    /**
     * Singletons should not be cloneable.
     */
    protected function __clone() { }

    /**
     * Singletons should not be restorable from strings.
     */
    public function __wakeup()
    {
        throw new \Exception("Cannot unserialize a singleton.");
    }

    /**
     * This is the static method that controls the access to the singleton
     * instance. On the first run, it creates a singleton object and places it
     * into the static field. On subsequent runs, it returns the client existing
     * object stored in the static field.
     *
     * This implementation lets you subclass the Singleton class while keeping
     * just one instance of each subclass around.
     */
    public static function getInstance(): Singleton
    {
        $cls = static::class;
        if (!isset(self::$instances[$cls])) {
            self::$instances[$cls] = new static();
        }

        return self::$instances[$cls];
    }

    /**
     * Finally, any singleton should define some business logic, which can be
     * executed on its instance.
     */
    public function someBusinessLogic()
    {
        // ...
    }
}

/**
 * The client code.
 */
function clientCode()
{
    $s1 = Singleton::getInstance();
    $s2 = Singleton::getInstance();
    if ($s1 === $s2) {
        echo "Singleton works, both variables contain the same instance.";
    } else {
        echo "Singleton failed, variables contain different instances.";
    }
}

clientCode();

Örnek Python Kodu

class SingletonMeta(type):
    """
    The Singleton class can be implemented in different ways in Python. Some
    possible methods include: base class, decorator, metaclass. We will use the
    metaclass because it is best suited for this purpose.
    """

    _instances = {}

    def __call__(cls, *args, **kwargs):
        """
        Possible changes to the value of the `__init__` argument do not affect
        the returned instance.
        """
        if cls not in cls._instances:
            instance = super().__call__(*args, **kwargs)
            cls._instances[cls] = instance
        return cls._instances[cls]


class Singleton(metaclass=SingletonMeta):
    def some_business_logic(self):
        """
        Finally, any singleton should define some business logic, which can be
        executed on its instance.
        """

        # ...


if __name__ == "__main__":
    # The client code.

    s1 = Singleton()
    s2 = Singleton()

    if id(s1) == id(s2):
        print("Singleton works, both variables contain the same instance.")
    else:
        print("Singleton failed, variables contain different instances.")

Diğer Tasarım Kalıpları/Design Patterns

Yaratımsal Kalıplar (Creational Patterns)

Yapısal Kalıplar (Structural Patterns)

Davranışsal Kalıplar (Behavioral Patterns)