child pointing to red interlocking brick toy

Singleton (Creational Patterns)

Nesnenin bulunduğu bağlam (context) içerisinde yalnızca bir defa oluşmasını istediğimiz durumlarda kullanılan örüntüdür. Bu örüntü ile mevcutta oluşmuş bir nesne var mı diye kontrol ederiz ve eğer yoksa oluştururuz. Daha önce oluşturulmuş bir nesne varsa mevcut nesneyi kullanırız.


Olması gerekenler;

  • Constructor private (eğer generic bir class oluşturuyorsak protected) olmalıdır. (Böylelikle new gibi anahtarlar {örn: Python’daki __init__} ile yeni nesne oluşumunu engelleriz.)
  • Yeni bir nesne oluşturmak yalnızca statik bir metod üzerinden mümkün olmalıdır. (Böylelikle tüm referansları tek bir noktaya toplarız ve eğer nesne yoksa bu metod içerisinde oluştururuz.)
  • Oluşan/oluşturulacak nesneyi tutması için private/protected (Erişim Belirleyici {Access Modifier}) nitelikte statik bir değişkenimiz olmalıdır.

Veritabanı bağlantıları vs. tüm singleton uygulamalarda durum yönetimi (state managment) çok önemlidir. Yani instance oluşturduğumuz zamanki durum güncel zamanda geçerli olmayabilir hatta güncel durum için hatalı nitelikte bile olabilir. Bu nedenle sınıflarımızı hazırlarken bunu asla unutmamalıyız.

Örnek Senaryolar

Senaryo1: Veritabanı bağlantılarında genellikle bu örüntü kullanılır. Eğer her sorgumuzda yeni bir bağlantı oluşmasını istemiyorsak bu örüntüyü kullanarak öncelikle mevcut bağlantının olup olmadığını kontrol edip eğer yoksa bağlantı oluşmasını varsa mevcut bağlantının kullanılmasını sağlayabiliriz.

Senaryo2: Config Manager gibi sınıflarda bu örüntü kullanılarak configlerin bir defa okunması sağlanır. Bu sayede her config read isteğimizde tüm configleri (dosyadan yada bir veritabanından) tekrar tekrar okumayız.

Dikkat edilmesi gerekenler;

Senaryo1: Biz T1 zamanında veritabanımıza bağlanmış olalım ve veritabanı connection timeout’umuz 30 saniye olsun. Çalıştırdığımız sorgu T1+30sn sonra sonuçlandığında artık veritabanı bağlantımız bir sonraki sorgu için kopmuş durumdadır. Bu nedenle hala bağlı olup olmadığınızı kontrol eden metodlar hazırlamalı ve bu gibi olası senaryoların düşünüldüğü kodlar yazmalısınız.

Senaryo2: T1 zamanında instance oluşturduk haliyle ayar dosyalarımızı okuduk ve bunları belleğe aldık. Şimdi artık bizden herhangi bir ayar değeri isteyen olursa belleğe aldığımız bu veriler ile dönüş yapacağız. Peki ya uygulamamız çalışırken ayarlarda bir güncelleme olduysa? Bu durumda yeni ayarları okuyabilmemiz için uygulamanın yeniden başlatılması gerekmektedir. Ama böyle olmamalı… Instance oluştururuken yani örneğin PHP’de __construct metodumuzda ayarların ne zaman okunduğuna dair bir zaman damgası kaydetmeliyiz. Sonra bizden her ayar değeri istendiğinde ayar dosyalarının en son ne zaman okunduğunu kontrol edip eğer üzerinden 30 dakika geçtiyse yeniden okunmasını sağlamamız gerekiyor. Bu durumda Time to Live (TTL) belirlemiş oluyoruz yani başlangıçta okuduğumuz ayarların bir yaşam süresi oluyor, o süre geçtiğinde artık geçersiz oluyorlar ve biz gidip tekrar ayarlarımızı okuyoruz.


Örnek Kod

<?php

class Database
{
    private static $_instance;

    private function __contruct()
    {
        // Burada bir defa çalışacak olan kodlarımız yer almalıdır.
    }

    /**
    * Singleton sınıflar klonlanamamalıdır.
    */
    protected function __clone()
    {
    }

    /**
    * Singleton sınıflar yeniden oluşturulmak üzere serialize edilmemelidir.
    */
    public function __sleep()
    {
        throw new \Exception('Singleton sınıflar yeniden oluşturulmak üzere serialize edilemez.');
    }

    /**
    * Singleton sınıflar yeniden oluşturulamaz.
    */
    public function __wakeup()
    {
        throw new \Exception('Bu sınıf yeniden oluşturulamaz.');
    }

    /**
    * Bu sınıfı kullanmak istediğimiz her yerde bu metodu çağıracağız.
    */
    public static function getInstance()
    {
        // Mevcut instance var mı diye bakıyoruz;
        if (!self::$_instance)
        {
            // Yoksa oluşturuyoruz;
            self::$_instance = new self();
        }

        // Oluşturulmuş instance'ı dönüyoruz.
        return self::$_instance;
    }
}