Teknik Detaylar

Singleton Tasarım Deseni Nedir?

← Teknik Detaylar
2021-08-17 · 6 dk okuma
Singleton Tasarım Deseni Nedir?
Bu yazıyı yapay zekâ ile tartış
Sayfayı kopyala

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.

💡 Özet (TL;DR):

  • Amaç: Bir sınıfın yalnızca tek bir örneğinin (instance) var olmasını garanti eder ve bu örneğe global bir erişim noktası sağlar.
  • Kilit Yapılar: Private constructor (dışarıdan nesne oluşturulmasını engeller) ve statik getInstance() metodu.
  • Eleştiri: Birçok modern mimaride test edilebilirliği zorlaştırması ve sıkı bağımlılık (tight coupling) yaratması sebebiyle bir anti-pattern olarak kabul edilmektedir. Modern projelerde alternatifi olarak Dependency Injection (DI) Container'lar kullanılır.

Singleton Deseninin Amacı

Singleton (Tekil), bir nesnenin sadece tek bir örneğinin olduğundan emin olmak ve bu nesneye ihtiyaç duyduğunuzda kodunuzun her yerinden aynı örneğin çağrılmasını sağlamak için kullanılan yaratımsal (creational) bir tasarım desenidir.


Sorun

Singleton deseni, SOLID programlama prensiplerinin en başında gelen "Tek Sorumluluk Prensibi"ne (Single Responsibility Principle) aykırılık oluşturma pahasına iki problemi tek seferde çözer:

1. Bir sınıfın tek bir örneğinin olduğundan emin olmak

Neden bir sınıfa ait sadece tek bir örneğin olması istenir? En sık karşılaşılan sebep; paylaşılan ortak bir kaynağa (örneğin bir veritabanı bağlantısı, bir dosya veya log sistemi) erişimi tek elden kontrol etmektir.

Normal bir oluşturucu (constructor) çağrısı her zaman yeni bir nesne döndürmek zorundadır. Ancak Singleton tasarımında, daha önce bir nesne oluşturulduysa tekrar yeni bir nesne oluşturulmaz, hafızadaki mevcut örnek istemciye geri dönüştürülür.

2. O örneğe global bir erişim noktası sağlamak

Uygulamalarda önemli nesneleri saklamak için kullanılan global değişkenler her zaman risk taşır. Herhangi bir kod parçası bu global değişkenlerin üzerine yazıp uygulamanın çökmesine neden olabilir.

Singleton deseni, nesneye tıpkı global bir değişken gibi her yerden erişmenizi sağlar ancak diğer kodların bu örneğin üzerine yazmasını (overwrite) engelleyerek güvenli bir koruma sunar.


Çözüm

Tüm Singleton uygulamalarında şu iki adım ortaktır:

  • Sınıfın varsayılan yapılandırıcısını (constructor) private (veya protected) yapın. Böylece diğer sınıflar new operatörünü kullanarak bu sınıftan nesne üretemez.
  • Constructor görevi görecek statik bir oluşturma metodu (genellikle getInstance()) yazın. Bu metot arka planda private constructor'ı çağırıp oluşturulan nesneyi statik bir değişkende saklar. Sonraki çağrılarda ise her zaman bu saklanan nesneyi geri döndürür.

Gerçek Hayat Senaryosu: Veritabanı Bağlantı Yöneticisi

Loglama veya DB bağlantısı gibi ortak kaynakların yönetiminde PHP üzerinde Singleton şu şekilde kurgulanır:

class DatabaseConnection {
    private static ?DatabaseConnection $instance = null;
    private PDO $connection;

    // 1. Dışarıdan 'new' ile çağrılmayı engelliyoruz
    private function __construct() {
        $this->connection = new PDO("mysql:host=localhost;dbname=test", "user", "pass");
    }

    // 2. Nesnenin kopyalanmasını (clone) engelliyoruz
    private function __clone() {}

    // 3. Serileştirilmiş dizeden nesne üretilmesini engelliyoruz
    public function __wakeup() {
        throw new \Exception("Singleton nesneleri deserialize edilemez.");
    }

    // 4. Tek erişim noktası (Lazy Initialization)
    public static function getInstance(): DatabaseConnection {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function getConnection(): PDO {
        return $this->connection;
    }
}

Singleton vs Statik Sınıflar (Static Class)

KriterSingletonStatik Sınıf
OOP UyumuArayüz (Interface) uygulayabilir, kalıtım (Inheritance) alabilir.Kalıtım ve arayüz uygulayamaz. OOP prensiplerine pek uymaz.
Yükleme ZamanıNesneye ilk ihtiyaç duyulduğunda oluşturulur (Lazy Loading).Uygulama başlarken direkt belleğe yüklenir (Eager Loading).
Bellek Yönetimiİhtiyaç kalmadığında bellekten temizlenebilir.Uygulama ayakta kaldığı sürece bellekte yer işgal eder.

Thread Safety (İş Parçacığı Güvenliği)

PHP, yapısı gereği tek iş parçacıklı (single-threaded) çalıştığı için eş zamanlı erişim sorunlarıyla karşılaşmaz. Ancak Python, Java veya C# gibi çoklu iş parçacığı (multi-threading) destekleyen dillerde iki farklı thread aynı anda getInstance() metoduna girdiğinde race condition oluşabilir ve iki farklı nesne üretilebilir.

Python'da bunu engellemek için bir Lock (Kilit) mekanizması kullanılır:

import threading

class SingletonMeta(type):
    _instances = {}
    _lock: threading.Lock = threading.Lock() # Kilit mekanizması

    def __call__(cls, *args, **kwargs):
        # Double-checked locking
        if cls not in cls._instances:
            with cls._lock:
                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):
        pass

Uygulanabilirlik

  • Ortak Kaynak Kontrolü: Veritabanı bağlantısı, log yazıcısı veya config dosyaları gibi tüm uygulamanın ortak kullanması gereken tekil kaynaklarda kullanın.
  • Sıkı Global Kontrol: Global değişkenlerin yaratacağı güvenlik açıklarını engellemek ve nesnenin üzerine yazılmasını kesin olarak önlemek istediğinizde kullanın.

Diğer Tasarım Desenleri ile İlişkisi

  • Facade sınıfları genellikle tek bir örneğe ihtiyaç duydukları için Singleton olarak tasarlanabilirler.
  • Paylaşılan tüm durumlar tek bir nesneye indirgenebilirse Flyweight deseni Singleton ile benzerlik gösterir. Ancak Flyweight nesneleri immutable (değiştirilemez) olmak zorundadır.
  • Abstract Factory, Builder ve Prototype desenleri çoğunlukla Singleton olarak uygulanabilir.

Singleton Tasarım Deseni Kod Örnekleri

Örnek PHP Kodu

<?php

namespace RefactoringGuru\Singleton\Conceptual;

class Singleton
{
    private static array $instances = [];

    protected function __construct() { }

    protected function __clone() { }

    public function __wakeup()
    {
        throw new \Exception("Singleton deserialize edilemez.");
    }

    public static function getInstance(): Singleton
    {
        $cls = static::class;
        if (!isset(self::$instances[$cls])) {
            self::$instances[$cls] = new static();
        }

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

    public function someBusinessLogic()
    {
        // İş mantığı kodları
    }
}

function clientCode()
{
    $s1 = Singleton::getInstance();
    $s2 = Singleton::getInstance();
    if ($s1 === $s2) {
        echo "Singleton çalışıyor, iki değişken de aynı örneği içeriyor.";
    } else {
        echo "Singleton başarısız, değişkenler farklı örneklere sahip.";
    }
}

clientCode();

Örnek Python Kodu

class SingletonMeta(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        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):
        pass


if __name__ == "__main__":
    s1 = Singleton()
    s2 = Singleton()

    if id(s1) == id(s2):
        print("Singleton çalışıyor, iki değişken de aynı örneği içeriyor.")
    else:
        print("Singleton başarısız, değişkenler farklı örneklere sahip.")

Sıkça Sorulan Sorular (FAQ)

Singleton neden bir anti-pattern olarak kabul ediliyor?

  • Test Edilebilirliği Zorlaştırır: Birim testlerinde (unit test) nesnelerin izole edilmesi istenir. Singleton global bir durum barındırdığı için bir testten kalan veriler diğer testleri etkileyebilir (test pollution). Mock edilmesi (taklit edilmesi) zordur.
  • Sıkı Bağımlılık Yaratır: Sınıfların içine doğrudan Singleton::getInstance() yazmak, o sınıfı Singleton sınıfına sıkı sıkıya bağımlı kılar.
  • SRP Prensibini Bozar: Sınıf hem kendi asıl işini yapar hem de kendi örneğinin yaşam döngüsünü kontrol eder.

Dependency Injection (DI) varken Singleton'a gerek var mı?

Modern framework mimarilerinde (Laravel, Symfony, Spring, NestJS vb.) Singleton deseni el ile kurulmaz. Bunun yerine Dependency Injection Container (Bağımlılık Enjeksiyonu Konteyneri) kullanılır. Sınıf normal şekilde tasarlanır ve DI container'a "Singleton" yaşam döngüsüyle kaydedilir. Nesnenin tek bir örneğinin olmasını ve yönetilmesini DI Container üstlenir. Bu sayede test edilebilir ve esnek bir mimari elde edilir.

PHP'de __clone ve __wakeup metotları neden private/protected yapılır?

PHP'de clone $instance çağrısı yapıldığında nesnenin kopyası oluşturulur. unserialize() çağrısı ise dizeye dönüştürülmüş nesneyi tekrar hayata döndürürken constructor'ı çalıştırmadan yeni bir nesne üretir. Bu durum tekillik ilkesini bozar. Bu nedenle bu iki sihirli metot (magic method) kapatılarak devre dışı bırakılır.


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

Oluşumsal Kalıplar (Creational Patterns)

Factory Method, Abstract Factory, Builder, Prototype, Singleton

Yapısal Kalıplar (Structural Patterns)

Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy

Davranışsal Kalıplar (Behavioral Patterns)

Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor