Singleton Pattern

Singleton Pattern, Object-Oriented Programming'daki en iyi bilinen yaratıcı Design Patternsnden biridir. Bir uygulamanın ömrü boyunca bir sınıfın yalnızca bir örneğe sahip olması ve bu örneğe kontrollü bir küresel erişim noktasından erişilebilmesi gerektiğinde kullanılır.

Singleton'un anlaşılması kolaydır ancak aynı zamanda en çok tartışılan Design Patternsnden biridir. Belirli durumlarda yararlı olabilir, ancak aşırı kullanıldığında gizli bağımlılıklar, test sorunları ve sıkı bir şekilde bağlanmış kodlar da oluşturabilir. Bu makalede Singleton Pattern'nun nasıl çalıştığı, ne zaman faydalı olabileceği, ne zaman kaçınılması gerektiği ve PHP'da nasıl uygulanacağı açıklanmaktadır.

Giriş

Bu Design Patterns serisinin önceki makalesinde, Design Patternsnin genel fikrini tanıttık ve geliştiricilerin yaygın yazılım tasarımı sorunlarını çözmelerine nasıl yardımcı olduklarını açıkladık. Ayrıca Design Patternsnın ana kategorilerini de tartıştık: yaratımsal, yapısal ve davranışsal kalıplar.

Singleton Pattern yaratıcı Design Patterns kategorisine aittir. Yaratılış kalıpları nesnelerin nasıl yaratıldığına ve nesne yaratımının nasıl kontrol edilebileceğine veya basitleştirilebileceğine odaklanır.

Singleton genellikle yapılandırma nesneleri, günlükçüler, önbellek yöneticileri, database bağlantı yöneticileri ve uygulama düzeyindeki hizmetler gibi paylaşılan kaynaklara erişimi denetlemek için kullanılır. Bununla birlikte, modern yazılım tasarımı, özellikle büyük ve test edilebilir uygulamalarda sıklıkla bağımlılık enjeksiyonunu doğrudan Singleton kullanımına tercih eder.

Singleton Pattern Nedir?

Singleton Pattern, bir sınıfı yalnızca bir nesne örneğiyle sınırlayan ve aynı örneğe uygulamanın farklı bölümlerinden erişmenin bir yolunu sağlayan bir tasarım modelidir.

Basit bir ifadeyle Singleton şu soruyu yanıtlıyor: Bir sınıftan yalnızca bir nesnenin var olduğundan nasıl emin olabiliriz?

Örneğin bir uygulamanın, uygulama ayarlarını saklayan bir konfigürasyon yöneticisine ihtiyacı olabilir. Singleton Pattern, birçok yapılandırma yöneticisi nesnesi oluşturmak yerine uygulamanın tüm bölümlerinin aynı örneği kullanmasını sağlar.

Singleton'un Ana Fikri

Singleton'un ana fikri üç kurala dayanmaktadır:

  • Sınıf kendi tek örneğini dahili olarak saklar.

  • Yapıcı, dışarıdan doğrudan nesne oluşturulmasını önlemek için özeldir veya korumalıdır.

  • Sınıf, tek örneği döndüren genel bir statik yöntem sağlar.

Yapıcı genel olarak erişilebilir olmadığından, harici kod new anahtar sözcüğünü kullanarak yeni nesneler oluşturamaz. Bunun yerine getInstance gibi bir yöntemi çağırması gerekir.

Singleton Neden Kullanılır?

Singleton, bir uygulamanın bir sınıfın tam olarak bir paylaşılan örneğine ihtiyacı olduğunda kullanılır. Bu, tekrarlanan nesne oluşturulmasını önlemeye yardımcı olabilir ve paylaşılan bir duruma veya paylaşılan kaynağa tutarlı bir şekilde erişilmesini sağlayabilir.

Singleton'u kullanmanın yaygın nedenleri şunlardır:

  • Paylaşılan bir kaynağa erişimi denetleme.

  • Bir konfigürasyon yöneticisinin bir örneğinin sağlanması.

  • Bir günlükçü örneğinin sağlanması.

  • Paylaşılan bir önbellek nesnesini yönetme.

  • Pahalı nesnelerin tekrar tekrar başlatılmasının önlenmesi.

Ancak geliştiricilerin dikkatli olması gerekiyor. Bir sınıfın Singleton olabilmesi, onun Singleton olması gerektiği anlamına gelmez. Desen gerçek bir tasarım problemini çözmelidir.

Temel Singleton Yapısı

Temel bir Singleton sınıfı genellikle özel bir statik özellik, özel bir kurucu ve örneğe erişim için genel bir statik yöntem içerir.

class Singleton
{
    private static ?Singleton $instance = null;

    private function __construct()
    {
    }

    public static function getInstance(): Singleton
    {
        if (self::$instance === null) {
            self::$instance = new Singleton();
        }

        return self::$instance;
    }
}

Bu örnekte statik özellik tek örneği saklar. GetInstance yöntemi, örneğin zaten mevcut olup olmadığını kontrol eder. Eğer mevcut değilse, yöntem onu ​​oluşturur. Zaten mevcutsa, yöntem aynı nesneyi döndürür.

Bu işleme bazen tembel başlatma denir çünkü nesne yalnızca ilk kez ihtiyaç duyulduğunda oluşturulur.

PHP'da Singleton Örneği

Aşağıdaki örnek, bir uygulama yapılandırma yöneticisi için daha pratik bir Singleton uygulamasını göstermektedir:

class ConfigManager
{
    private static ?ConfigManager $instance = null;

    private array $settings = [];

    private function __construct()
    {
        $this->settings = [
            'app_name' => 'My Application',
            'environment' => 'production',
            'debug' => false,
        ];
    }

    public static function getInstance(): ConfigManager
    {
        if (self::$instance === null) {
            self::$instance = new ConfigManager();
        }

        return self::$instance;
    }

    public function get(string $key): mixed
    {
        return $this->settings[$key] ?? null;
    }
}

$config = ConfigManager::getInstance();
echo $config->get('app_name');

Bu örnekte ConfigManager sınıfı yalnızca bir kez oluşturulabilir. Uygulamanın ConfigManager::getInstance'ı çağıran herhangi bir kısmı aynı örneği alacaktır.

Singleton'da Klonlamayı Önleme

PHP'da clone anahtar sözcüğü kullanılarak bir nesne klonlanabilir. Singleton'un kopyalanmasını önlemek için geliştiriciler genellikle __clone yöntemini özel yaparak klonlamayı önler.

class ConfigManager
{
    private static ?ConfigManager $instance = null;

    private function __construct()
    {
    }

    private function __clone()
    {
    }

    public static function getInstance(): ConfigManager
    {
        if (self::$instance === null) {
            self::$instance = new ConfigManager();
        }

        return self::$instance;
    }
}

__clone özel hale getirildiğinde harici kod Singleton nesnesini kopyalayamaz.

Singleton'da Serileştirmeyi Önleme

Bir nesneyi yeniden yaratmanın başka bir yolu da serileştirme ve serileştirmeyi kaldırmadır. Modern PHP sürümlerinde geliştiriciler, istisna oluşturan bir __wakeup yöntemi tanımlayarak serileştirmenin kaldırılmasını önleyebilir.

class ConfigManager
{
    private static ?ConfigManager $instance = null;

    private function __construct()
    {
    }

    private function __clone()
    {
    }

    public function __wakeup(): void
    {
        throw new Exception('Cannot unserialize a singleton.');
    }

    public static function getInstance(): ConfigManager
    {
        if (self::$instance === null) {
            self::$instance = new ConfigManager();
        }

        return self::$instance;
    }
}

Bu, Singleton'un seri durumdan çıkarma yoluyla yeniden oluşturulmasına karşı korunmasına yardımcı olur.

Tembel Başlatma

Tembel başlatma, Singleton örneğinin yalnızca ilk kez istendiğinde oluşturulduğu anlamına gelir. Bu, nesneyi oluşturmanın pahalı olduğu durumlarda veya bir istek sırasında nesneye her zaman ihtiyaç duyulmayabileceği durumlarda yararlı olabilir.

Temel Singleton uygulamasında, getInstance yönteminin içinde yavaş başlatma gerçekleşir. GetInstance çağrılana kadar nesne oluşturulmaz.

Bu, gereksiz kaynak kullanımını azaltabilir ancak aynı zamanda başlatma mantığının sınıfın içinde gizlendiği anlamına da gelir. Daha büyük uygulamalarda bağımlılık enjeksiyonu, nesnelerin ne zaman ve nasıl oluşturulduğu konusunda daha net kontrol sağlayabilir.

İstekli Başlatma

İstekli başlatma, Singleton örneğinin talep edilmeden önce oluşturulduğu anlamına gelir. Bu yaklaşım PHP web uygulamalarında daha az yaygındır çünkü PHP istekleri genellikle kısa ömürlüdür ancak kavram diğer ortamlarda faydalıdır.

İstekli başlatmayla nesne hemen var olur. Bu, erişimi kolaylaştırabilir ancak kullanılmayan nesneler oluşturabilir ve başlangıç ​​maliyetini artırabilir.

Tembel başlatma genellikle PHP Singleton örneklerinde daha yaygındır.

Singleton'un Gerçek Kullanım Durumları

Singleton Pattern, bazı gerçek yazılım durumlarında, özellikle de tam olarak bir örneğe ihtiyaç duyulduğunda ve nesnenin sık sık değiştirilmesi gerekmediğinde yararlı olabilir.

Olası kullanım durumları şunları içerir:

  • Yapılandırma yöneticisi: Uygulama ayarlarını okuyan ve sağlayan paylaşılan bir nesne.

  • Logger: Uygulama genelinde kullanılan merkezi bir günlük kaydı nesnesi.

  • Önbellek yöneticisi: Paylaşılan bir önbellek erişim nesnesi.

  • Uygulama kaydı: Paylaşılan uygulama düzeyindeki değerler için merkezi bir yer.

  • Kaynak yöneticisi: Sınırlı bir kaynağa erişimi kontrol eden bir sınıf.

Bu durumlarda bile geliştiricilerin bağımlılık enjeksiyonunun mu yoksa hizmet konteynerinin mi daha temiz bir tasarım sağlayacağını değerlendirmeleri gerekir.

Logger Örneği için Singleton

Uygulamanın bir paylaşılan günlük kaydı örneğine ihtiyacı olduğunda basit bir günlükçü Singleton olarak uygulanabilir.

class Logger
{
    private static ?Logger $instance = null;

    private function __construct()
    {
    }

    public static function getInstance(): Logger
    {
        if (self::$instance === null) {
            self::$instance = new Logger();
        }

        return self::$instance;
    }

    public function log(string $message): void
    {
        echo date('Y-m-d H:i:s') . ' - ' . $message;
    }
}

$logger = Logger::getInstance();
$logger->log('Application started.');

Bu örnek basittir ancak gerçek uygulamalarda bir kaydedici dosyalara, databases'ya, bulut hizmetlerine veya harici izleme araçlarına yazabilir. Bu gibi durumlarda, enjekte edilmiş bir günlükçü arayüzünün kullanılması, bir Singleton günlükçüsünün sabit kodlamasından genellikle daha esnektir.

Singleton Pattern'nun Faydaları

Doğru kullanıldığında Singleton Pattern'nun birçok avantajı vardır. Geliştiricilere nesne oluşturma üzerinde kontrol sağlar ve yalnızca bir örneğin mevcut olmasını sağlar.

Ana faydalar şunları içerir:

  • Bir sınıfın tek bir paylaşılan örneğini sağlar.

  • Bu örneğe küresel bir erişim noktası sağlar.

  • Tekrarlanan nesne oluşumunu azaltabilir.

  • Paylaşılan kaynaklara erişimi merkezileştirebilir.

  • Küçük uygulamalarda uygulanması basit olabilir.

Bu avantajlar Singleton'u yeni başlayanlar için çekici kılmaktadır ancak modelin önemli dezavantajları da vardır.

Singleton Pattern'nun dezavantajları

Singleton Pattern çok sık veya yanlış yerde kullanılırsa sorun yaratabilir. En büyük sorunlardan biri uygulamaya küresel durumu dahil etmesidir.

Global durum, uygulamanın herhangi bir bölümünün aynı nesneye erişebilmesi ve onu değiştirebilmesi nedeniyle kodun anlaşılmasını zorlaştırabilir. Bu, gizli bağımlılıklara ve beklenmeyen davranışlara yol açabilir.

Singleton ayrıca testi zorlaştırabilir. Bir sınıf doğrudan ConfigManager::getInstance veya Logger::getInstance'ı çağırırsa, birim testleri sırasında bu bağımlılığı sahte bir nesneyle değiştirmek zorlaşır.

Diğer bir sorun ise sıkı bağlantıdır. Doğrudan bir Singleton sınıfına bağlı olan kod, söz konusu uygulamaya güçlü bir şekilde bağlanır.

Singleton ve Test Sorunları

Test, birçok geliştiricinin modern uygulamalarda Singleton'dan kaçınmasının ana nedenlerinden biridir. Birim testleri, bağımlılıklar kolayca değiştirilebildiğinde en iyi sonucu verir.

Bir sınıf bağımlılık enjeksiyonunu kullanıyorsa, test sahte bir bağımlılık sağlayabilir. Ancak bir sınıf doğrudan Singleton'u çağırırsa bu bağımlılığı değiştirmek daha zor hale gelir.

Örneğin, Logger::getInstance'ı çağıran bir hizmet doğrudan Logger sınıfına bağlıdır. Daha iyi bir tasarım, LoggerInterface'e bağlı olmak ve kaydediciyi dışarıdan enjekte etmek olabilir.

Singleton vs Dependency Injection

Bağımlılık enjeksiyonu, modern yazılım tasarımında genellikle Singleton'a daha iyi bir alternatiftir. Bir sınıftan bağımlılığını küresel bir erişim noktasından almasını istemek yerine bağımlılık sınıfa dışarıdan aktarılır.

Örneğin bunun yerine:

class OrderService
{
    public function completeOrder(): void
    {
        $logger = Logger::getInstance();
        $logger->log('Order completed.');
    }
}

Bir bağımlılık enjeksiyon yaklaşımı şöyle görünecektir:

class OrderService
{
    public function __construct(
        private LoggerInterface $logger
    ) {
    }

    public function completeOrder(): void
    {
        $this->logger->log('Order completed.');
    }
}

İkinci yaklaşım daha esnektir ve test edilmesi daha kolaydır. Hizmet, kaydedicinin bir dosya kaydedici mi, database kaydedici mi, bulut kaydedici mi yoksa sahte test kaydedici mi olduğunu bilmiyor.

Singleton ve Statik Sınıf

Singleton ve statik sınıflar benzer görünebilir çünkü her ikisine de çağıran kodda nesneler manuel olarak oluşturulmadan erişilebilir. Ancak bunlar aynı değildir.

Singleton hala bir nesnedir. Arayüzleri uygulayabilir, mirası kullanabilir ve nesne durumunu tutabilir. Statik bir sınıfa doğrudan statik yöntemlerle erişilir ve genellikle bir nesne örneği gerektirmez.

Statik sınıflar genellikle basit yardımcı yöntemler için kullanılır. Singleton, bir nesne örneğinin mevcut olması gerektiğinde kullanılır. Her iki yaklaşım da dikkatli kullanılmalıdır çünkü aşırı kullanılırsa eşleşmeyi artırabilirler.

Modern Çerçevelerde Singleton

Laravel ve Symfony gibi modern çerçeveler, nesne yaşam sürelerini yönetmek için sıklıkla hizmet kapsayıcılarını kullanır. Bir hizmet kapsayıcısı, bir sınıfı paylaşılan bir hizmet olarak kaydedebilir; bu, aynı örneğin istendiğinde yeniden kullanıldığı anlamına gelir.

Bu, sınıfın kendisini Singleton Pattern'yu uygulamaya zorlamadan Singleton benzeri davranış sağlar.

Bu yaklaşım genellikle daha iyidir çünkü sınıf temiz ve test edilebilir kalır. Bir örneği paylaşma kararı, sınıf içinde sabit kodlanmak yerine çerçeve yapılandırması veya hizmet kapsayıcısı tarafından gerçekleştirilir.

Laravel'da Singleton

Laravel'da geliştiriciler, hizmet kapsayıcısını kullanarak bir sınıfı bir hizmet sağlayıcının içinde tekil olarak bağlayabilirler:

$this->app->singleton(LoggerInterface::class, FileLogger::class);

Bu, Laravel'ya söz konusu bağlama için yalnızca bir paylaşılan örnek oluşturmasını söyler. Diğer sınıflar LoggerInterface'e bağlı olabilir ve Laravel aynı FileLogger örneğini enjekte edecektir.

Bu, manuel bir Singleton sınıfı yazmaktan farklıdır. Sınıfın kendisinin özel bir kurucuya veya getInstance yöntemine ihtiyacı yoktur. Container, nesnenin ömrünü kontrol eder.

Singleton Ne Zaman Kullanılır?

Singleton Pattern'yu yalnızca bir sınıfın tam olarak bir örneğinin var olduğunu garanti etmek için güçlü bir neden olduğunda ve genel erişimin tasarıma zarar vermediği durumlarda kullanın.

Singleton şu durumlarda kabul edilebilir:

  • Nesne, gerçek anlamda paylaşılan uygulama düzeyinde bir kaynağı temsil eder.

  • Mantıksal olarak yalnızca bir örnek bulunmalıdır.

  • Testlerde nesnenin sıklıkla değiştirilmesine gerek yoktur.

  • Uygulama küçüktür ve karmaşık bağımlılık yönetimi gerektirmez.

  • Çerçeve hizmet kapsayıcısı mevcut değil.

O zaman bile geliştiricilerin onu kullanmadan önce dikkatlice düşünmesi gerekir.

Singleton'dan Ne Zaman Kaçınılmalı?

Sınıfın değişebilecek bağımlılıkları olduğunda, kodun test edilmesinin kolay olması gerektiğinde veya gelecekte birden fazla uygulamaya ihtiyaç duyulabileceği durumlarda Singleton'dan kaçının.

Singleton'dan genellikle şu durumlarda kaçınılmalıdır:

  • Sınıf dış hizmetlere bağlıdır.

  • Test sırasında sınıfın sahte veya sahte bir sınıfla değiştirilmesi gerekir.

  • Uygulama bağımlılık enjeksiyonunu veya bir hizmet kapsayıcısını kullanır.

  • Singleton gizli küresel durum yaratır.

  • Daha sonra birden fazla konfigürasyona veya birden fazla örneğe ihtiyaç duyulabilir.

  • Desen yalnızca kolaylık sağlamak için kullanılır.

Singleton'u yalnızca kolay erişim için kullanmak genellikle zayıf tasarımın işaretidir.

Singleton Pattern için En İyi Uygulamalar

Singleton kullanılıyorsa dikkatli bir şekilde ve yalnızca sınırlı yerlerde uygulanmalıdır.

Yararlı en iyi uygulamalar şunları içerir:

  • Singleton'ı yalnızca mantıksal olarak bir örnek gerekli olduğunda kullanın.

  • Singleton sınıflarını basit ve odaklanmış tutun.

  • Özel bir kurucu kullanarak doğrudan inşaatı önleyin.

  • Yinelenen örneklere izin verilmiyorsa klonlamayı önleyin.

  • Gerektiğinde serileştirmenin kaldırılmasını önleyin.

  • Çok fazla değişken küresel durum depolamaktan kaçının.

  • Büyük uygulamalarda bağımlılık enjeksiyonunu tercih edin.

  • Mümkün olduğunda çerçeve hizmet kapsayıcılarını kullanın.

Bu uygulamalar Singleton risklerini azaltmaya ve uygulama tasarımının daha temiz kalmasına yardımcı olur.

Singleton'da Yaygın Hatalar

Yaygın bir hata, erişimi kolay olduğundan her hizmet için Singleton kullanmaktır. Bu, gizli bir küresel yapı oluşturur ve uygulamanın test edilmesini zorlaştırır.

Diğer bir hata ise bağımlılık enjeksiyonunu öğrenmekten kaçınmak için Singleton kullanmaktır. Singleton ilk başta daha basit görünse de uzun vadeli bakım sorunları yaratabilir.

Üçüncü bir hata, isteğe özel verileri bir Singleton'da saklamaktır. Paylaşılan örnekler, yalnızca bir kullanıcı isteğine veya tek bir işleme ait verileri yanlışlıkla saklamamalıdır.

Dördüncü hata ise Singleton'u iyi mimarinin alternatifi olarak görmektir. Singleton nesne oluşturmayı kontrol eder, ancak tasarımı otomatik olarak temizlemez.

Singleton ve Konu Güvenliği

Bazı programlama dillerinde ve çalışma zamanı ortamlarında Singleton uygulamasının iş parçacığı güvenliğini dikkate alması gerekir. Birden fazla iş parçacığı aynı anda örneği oluşturmaya çalışırsa, kod korunmadığı sürece birden fazla nesne oluşturulabilir.

Tipik PHP web uygulamalarında her istek genellikle ayrı olarak çalışır, dolayısıyla iş parçacığı güvenliği Java veya C# gibi dillere göre daha az endişe vericidir. Ancak uzun süredir çalışan PHP işlemleriyle, çalışanlarla veya eşzamansız ortamlarla çalışan geliştiricilerin yine de nesne ömrünü ve paylaşılan durumu dikkatle anlamaları gerekir.

Küçük Projelerde Singleton ve Büyük Projeler

Küçük komut dosyalarında veya basit uygulamalarda, Singleton, yapılandırma veya basit paylaşılan yardımcı programlar için kabul edilebilir. Uygulanması kolay ve anlaşılması kolay olabilir.

Büyük projelerde Singleton genellikle daha az uygundur çünkü bağımlılıkları gizleyebilir ve testi zorlaştırabilir. Büyük uygulamalar genellikle bağımlılık enjeksiyonundan, arayüzlerden, hizmet kapsayıcılarından ve net nesne yaşam sürelerinden daha fazla yararlanır.

Projenin boyutu ve karmaşıklığı kararı etkilemelidir. Küçük bir komut dosyasında kabul edilebilir bir kalıp, büyük bir kurumsal uygulamada sorun yaratabilir.

Singleton'u Kullanmadan Önce Pratik Kontrol Listesi

Singleton Pattern'yu kullanmadan önce geliştiricilerin şu soruları sorması gerekir:

  • Bu sınıfın gerçekten yalnızca bir örneğe ihtiyacı var mı?

  • Bu nesne için genel erişim güvenli mi?

  • Bu testi zorlaştıracak mı?

  • Bağımlılık enjeksiyonu sorunu daha iyi çözebilir mi?

  • Bu nesne değiştirilebilir paylaşılan durumu depolayacak mı?

  • Uygulamanın daha sonra birden fazla örneğe ihtiyacı olabilir mi?

  • Bu model tasarım nedeniyle mi yoksa yalnızca kolaylık sağlamak için mi kullanılıyor?

Cevaplar Singleton'un değerden çok risk kattığını gösteriyorsa başka bir tasarım yaklaşımı kullanılmalıdır.

Sonuç

Singleton Pattern, bir sınıfın yalnızca bir örneğe sahip olmasını ve bu örneğe küresel bir erişim noktası sağlamasını sağlayan yaratıcı bir tasarım modelidir. Belirli durumlarda yapılandırma yöneticileri, günlükçüler ve önbellek yöneticileri gibi paylaşılan kaynaklar için yararlı olabilir.

Ancak Singleton dikkatli kullanılmalıdır. Açık bir neden olmadan uygulandığında gizli bağımlılıklar, küresel durum, sıkı bağlantı ve test zorlukları yaratabilir. Birçok modern uygulamada bağımlılık enjeksiyonu ve hizmet kapsayıcıları daha temiz ve daha esnek bir alternatif sağlar.

Design Patternsni öğrenen geliştiriciler için Singleton'un anlaşılması önemlidir çünkü nesne oluşturma ve paylaşılan örneklerle ilgili temel fikirleri sunar. Ancak profesyonel yazılım tasarımı, yalnızca Singleton'un nasıl uygulanacağını değil aynı zamanda bundan ne zaman kaçınılacağını da bilmeyi gerektirir.