Takip et

Singleton Tasarım Deseni Nedir?

Yazılım geliştirme dünyasında, karşılaşılan ortak problemleri çözmek için kanıtlanmış, tekrar kullanılabilir çözümler sunan tasar

Yazılım geliştirme dünyasında, karşılaşılan ortak problemleri çözmek için kanıtlanmış, tekrar kullanılabilir çözümler sunan tasarım desenleri, mimari bir yol haritası görevi görür. Bu desenler, kodun okunabilirliğini, sürdürülebilirliğini ve genişletilebilirliğini artırır. Tasarım desenleri arasında en temel ve sıklıkla yanlış anlaşılanlardan biri de Singleton (Tekil Nesne) desenidir. Bu rehberde, Singleton tasarım desenini JavaScript özelinde detaylı bir şekilde inceleyecek, nasıl uygulanacağını gösterecek, ne zaman kullanılması gerektiğini tartışacak ve potansiyel tuzaklara değineceğiz.

Singleton Tasarım Deseni Nedir?

Singleton tasarım deseni, bir sınıfın yalnızca bir örneğinin (instance) olmasını ve bu tek örneğe global bir erişim noktası sağlamasını garanti eden yaratımsal (creational) bir desendir. Temel amacı, bir uygulamanın yaşam döngüsü boyunca belirli bir kaynağın veya hizmetin sadece bir kez başlatılmasını ve tüm bileşenler tarafından paylaşılmasını sağlamaktır. Bu, özellikle kaynak yoğun nesnelerin (örneğin, veritabanı bağlantıları, konfigürasyon yöneticileri veya loglama servisleri) performans ve tutarlılık açısından tek bir noktadan yönetilmesini gerektiren senaryolarda kritik öneme sahiptir.

Singleton’ın temel prensipleri şunlardır:

  • Tek Örnek Garantisi: Sınıfın birden fazla örneğinin oluşturulmasını engeller.
  • Global Erişim Noktası: Oluşturulan tek örneğe her yerden erişim imkanı sunar.

JavaScript, sınıf tabanlı bir dil olmasa da (prototip tabanlıdır), ES6 ile birlikte gelen class anahtar kelimesi ve fonksiyonel programlama yetenekleri sayesinde Singleton desenini çeşitli yollarla uygulamak mümkündür.

JavaScript’te Singleton Uygulamaları

JavaScript’te Singleton deseni için birden fazla uygulama yaklaşımı bulunmaktadır. Her bir yaklaşım, dilin farklı özelliklerinden yararlanır ve farklı senaryolar için uygun olabilir.

Klasik Yaklaşım (Kurucu Fonksiyon ile)

Bu yaklaşım, Singleton’ı bir kurucu fonksiyon ve bir closure (kapanım) kullanarak oluşturur. Closure, tekil örneği depolamak için kullanılır ve bu örnek, dışarıdan doğrudan erişilemeyen özel bir değişken içinde saklanır.


function Logger() {
    // Örnek zaten var mı kontrol et
    if (Logger.instance) {
        return Logger.instance;
    }

    // Yeni bir örnek oluştur
    this.logs = [];
    Logger.instance = this; // Yeni örneği kaydet

    console.log("Logger örneği oluşturuldu.");

    // Public metodlar
    this.log = function(message) {
        const timestamp = new Date().toISOString();
        this.logs.push([${timestamp}] ${message});
        console.log(LOG: ${message});
    };

    this.getLogs = function() {
        return this.logs;
    };
}

// Kullanım
const logger1 = new Logger();
const logger2 = new Logger();

console.log(logger1 === logger2); // true - Aynı örnek

logger1.log("Uygulama başlatıldı.");
logger2.log("Kullanıcı girişi denemesi.");

console.log(logger1.getLogs());
console.log(logger2.getLogs()); // Her iki loglayıcı da aynı logları içerir.

Bu örnekte, Logger kurucu fonksiyonu her çağrıldığında, Logger.instance statik özelliğini kontrol eder. Eğer bir örnek zaten varsa, mevcut örneği döndürür. Aksi takdirde, yeni bir örnek oluşturur ve Logger.instance‘a atar. Bu sayede, her zaman aynı Logger örneğine sahip olunur.

ES6 Sınıfları ile Yaklaşım

ES6 ile gelen class anahtar kelimesi, daha geleneksel bir sınıf tanımı yapısı sunar. Singleton, bu yapıda statik metotlar ve özellikler kullanılarak uygulanabilir.


class ConfigManager {
    constructor() {
        if (ConfigManager.instance) {
            return ConfigManager.instance;
        }

        this.settings = {
            apiBaseUrl: "https://api.example.com",
            timeout: 5000,
            debugMode: false
        };
        ConfigManager.instance = this;
        console.log("ConfigManager örneği oluşturuldu.");
    }

    getSetting(key) {
        return this.settings[key];
    }

    setSetting(key, value) {
        this.settings[key] = value;
        console.log(Ayarlar güncellendi: ${key} = ${value});
    }

    static getInstance() {
        if (!ConfigManager.instance) {
            ConfigManager.instance = new ConfigManager();
        }
        return ConfigManager.instance;
    }
}

// Kullanım
const config1 = ConfigManager.getInstance();
const config2 = ConfigManager.getInstance();

console.log(config1 === config2); // true - Aynı örnek

console.log(config1.getSetting("apiBaseUrl"));
config2.setSetting("debugMode", true);
console.log(config1.getSetting("debugMode")); // true

Bu yaklaşımda, sınıfın kurucusu doğrudan çağrıldığında (new ConfigManager()), yine bir Singleton davranışı sergiler. Ancak, yaygın kullanım şekli static getInstance() metodunu kullanmaktır. Bu metot, örneği tembelce (lazy) başlatır; yani, sadece ilk ihtiyaç duyulduğunda oluşturur.

Modül Yaklaşımı (En Yaygın ve İdeal)

Modern JavaScript’te (özellikle ES Modülleri ile), Singleton desenini uygulamanın en doğal ve yaygın yolu modül deseni kullanmaktır. JavaScript modülleri, doğası gereği Singleton davranışını destekler. Bir modül bir kez içe aktarıldığında (imported), kodu sadece bir kez yürütülür ve modülün dışa aktardığı nesneler (export edilenler) uygulamanın her yerinde aynı örneği temsil eder.

cacheService.js


// cacheService.js
const cache = {};

const CacheService = {
    get(key) {
        console.log(Cache'ten okunuyor: ${key});
        return cache[key];
    },
    set(key, value) {
        console.log(Cache'e yazılıyor: ${key});
        cache[key] = value;
    },
    clear(key) {
        console.log(Cache temizleniyor: ${key});
        delete cache[key];
    },
    getAll() {
        return { ...cache }; // Önbelleğin bir kopyasını döndür
    }
};

export default CacheService; // Modülü dışa aktar

app.js


// app.js
import CacheService from './cacheService.js';
import { someFunctionInAnotherModule } from './anotherModule.js';

CacheService.set("userData", { id: 1, name: "Alice" });

// Başka bir yerden çağrı
someFunctionInAnotherModule();

const data = CacheService.get("userData");
console.log("Uygulamadan okunan data:", data);

anotherModule.js


// anotherModule.js
import CacheService from './cacheService.js';

export function someFunctionInAnotherModule() {
    const config = CacheService.get("appConfig");
    if (!config) {
        CacheService.set("appConfig", { theme: "dark", lang: "tr" });
    }
    console.log("Başka bir modülden cache kontrolü yapıldı.");
}

Bu yaklaşımda, CacheService nesnesi yalnızca bir kez oluşturulur ve export default ile dışa aktarılır. Uygulamanın herhangi bir yerinden import CacheService from './cacheService.js'; ile içe aktarıldığında, her zaman aynı nesneye referans edilir. Bu, modern JavaScript uygulamalarında Singleton davranışını sağlamanın en temiz ve en çok tercih edilen yoludur.

Singleton Ne Zaman Kullanılmalı?

Singleton deseni, belirli senaryolarda oldukça faydalı olabilir:

  • Veritabanı Bağlantıları veya Havuzları

    Bir uygulamanın tek bir veritabanı bağlantı havuzuna sahip olması performansı artırır ve kaynakları daha verimli kullanır. Her istek için yeni bir bağlantı açmak yerine, mevcut havuzdan bir bağlantı alınır.

  • Loglama Servisleri

    Tüm uygulamanın olayları tek bir merkezi loglayıcıya göndermesi gerektiğinde Singleton idealdir. Bu sayede tüm loglar tutarlı bir formatta toplanır ve yönetilir.

  • Konfigürasyon Yöneticileri

    Uygulama genelindeki ayarların (API anahtarları, ortam değişkenleri vb.) tek bir yerden okunması ve güncellenmesi gerektiğinde Singleton kullanılır. Bu, ayarların tutarlılığını garanti eder.

  • Önbellekleme Servisleri

    Uygulama içinde sıkça erişilen verileri saklamak için tek bir önbellek örneği kullanmak performansı artırır ve bellek yönetimini kolaylaştırır.

  • Merkezi State Yönetimi

    Redux store gibi merkezi durum yöneticileri, aslında birer Singleton olarak hareket eder. Tüm uygulama state’i tek bir yerde tutulur ve güncellenir.

  • Cihaz Sürücüleri

    Fiziksel bir cihaza (örneğin, bir yazıcı) erişim sağlayan bir sürücü, genellikle tek bir örneğe sahip olmalıdır.

Singleton’ın Avantajları ve Dezavantajları

Her tasarım deseni gibi, Singleton’ın da kendine özgü avantajları ve dezavantajları vardır. Bunları bilmek, deseni doğru senaryolarda kullanmak için kritik öneme sahiptir.

Avantajları

  • Tek Örnek Garantisi: Bir sınıfın yalnızca bir örneğinin olduğundan emin olmanın tek yoludur. Bu, kaynak yönetiminde ve tutarlılıkta büyük fayda sağlar.
  • Global Erişim Noktası: İhtiyaç duyulan her yerden kolayca erişilebilir. Karmaşık bağımlılık enjeksiyon sistemlerine gerek kalmadan kaynaklara erişim sağlar.
  • Kaynak Tüketimini Azaltır: Özellikle ağır nesnelerin (veritabanı bağlantıları, dosya sistemleri) birden çok kez oluşturulmasını engelleyerek sistem kaynaklarını korur.
  • Merkezi Kontrol: Belirli bir kaynağın veya hizmetin yaşam döngüsü ve durumu tek bir nokta üzerinden yönetilir.

Dezavantajları

  • Global Durum Yaratır: Singleton’lar global durum oluşturur ve bu durum uygulamanın her yerinden değiştirilebilir. Bu, özellikle büyük uygulamalarda beklenmedik yan etkilere ve hatalara yol açabilir.
  • Test Edilebilirliği Zorlaştırır: Birim testleri yazarken, Singleton’a bağımlı bileşenleri izole etmek zordur. Singleton’ın global durumu, testler arasında birikerek testlerin deterministik olmamasına neden olabilir. Mock’lama veya sahte nesneler kullanmak karmaşıklığı artırır.
  • Sıkı Bağımlılıklar (Tight Coupling): Singleton’ı kullanan sınıflar, doğrudan Singleton sınıfına bağımlı hale gelir. Bu, kodun yeniden kullanılabilirliğini azaltır ve mimariyi katılaştırır. Bağımlılık Enjeksiyonu (Dependency Injection) gibi desenlerle çelişir.
  • Sorumlulukların Ayrımı Prensibini İhlal Edebilir: Bir sınıfın kendi örneğini yönetme ve aynı zamanda iş mantığını yürütme sorumluluğunu taşıması, Tek Sorumluluk Prensibi’ne (Single Responsibility Principle) aykırı olabilir.
  • Gizli Bağımlılıklar: Bir fonksiyon veya sınıfın bir Singleton kullandığı, imzasından (parametresinden) belli olmayabilir. Bu da kodun anlaşılmasını ve hata ayıklamasını zorlaştırır.
  • Çoklu İş Parçacığı Sorunları (JavaScript için daha az): Geleneksel olarak, Singleton deseninin çoklu iş parçacığı ortamlarında (multi-threaded environments) dikkatli bir şekilde uygulanması gerekir (thread-safe). JavaScript tek iş parçacıklı olduğundan bu sorun daha az belirgin olsa da, Web Workers gibi durumlar veya asenkron işlemler sırasında yine de dikkatli olunmalıdır.

Sonuç ve Sıkça Sorulan Sorular

Singleton tasarım deseni, bir sınıfın yalnızca bir örneğinin olmasını ve bu örneğe global bir erişim noktası sağlamasını garanti eden güçlü bir araçtır. JavaScript’te, özellikle ES6 modülleri aracılığıyla, bu davranışa doğal yollardan ulaşmak mümkündür. Ancak, global durum oluşturma, test edilebilirliği zorlaştırma ve sıkı bağımlılıklar yaratma gibi potansiyel dezavantajları nedeniyle dikkatli kullanılmalıdır.

Modern JavaScript geliştirme pratiklerinde, modüllerin sunduğu doğal Singleton benzeri davranış ve Bağımlılık Enjeksiyonu gibi yaklaşımlar, birçok durumda Singleton deseninin manuel olarak uygulanmasına olan ihtiyacı azaltmıştır. Yine de, veritabanı bağlantıları, loglama servisleri veya konfigürasyon yöneticileri gibi belirli senaryolarda Singleton hala geçerli ve etkili bir çözüm olabilir.

Sıkça Sorulan Sorular (SSS)

Singleton bir anti-pattern midir?

Singleton, bazı geliştiriciler tarafından bir anti-pattern olarak görülür çünkü global durum yaratır, test edilebilirliği zorlaştırır ve sıkı bağımlılıklara yol açabilir. Ancak, doğru senaryolarda (örneğin, tek bir veritabanı bağlantı havuzu veya loglayıcı gibi kaynak yönetimi) hala değerli bir desendir. Önemli olan, potansiyel dezavantajlarını anlamak ve bilinçli bir karar vermektir.

Singleton ve Modül Deseni arasındaki fark nedir?

Modül deseni (özellikle ES Modülleri), JavaScript’te doğal olarak Singleton benzeri bir davranış sağlar. Bir ES modülü bir kez yüklendiğinde, içindeki tüm dışa aktarılan değerler (fonksiyonlar, nesneler vb.) uygulamanın her yerinde aynı örneği temsil eder. Singleton deseni ise daha çok “bir sınıfın tek bir örneğini garanti etme” kavramına odaklanırken, modül deseni daha geniş bir kapsama sahiptir ve kod organizasyonu ile bilgi gizleme (encapsulation) için kullanılır. Modern JS’de, çoğu zaman bir modülü dışa aktararak Singleton davranışı elde etmek en temiz yoldur.

JavaScript’te Singleton’ı test etmek zor mudur?

Evet, Singleton’ı test etmek zor olabilir çünkü global durum yaratır ve testler arasında etkileşime neden olabilir. Birim testlerinde, her testin izole olması ve birbirinden etkilenmemesi istenir. Singleton’ın global durumu bu prensibi bozabilir. Bu durumu aşmak için mock’lama kütüphaneleri kullanılabilir veya Singleton’ın test ortamında yeniden başlatılabilir bir mekanizması tasarlanabilir. Ancak, modül deseni ile uygulanan Singleton’lar, modüllerin import/export mekanizması sayesinde daha kolay mock’lanabilir olabilir.

ES6 modülleri Singleton davranışını nasıl sağlar?

ES6 modülleri, her modülün sadece bir kez değerlendirilmesini (parse edilmesini ve çalıştırılmasını) garanti eder. Bir modülün dışa aktardığı herhangi bir şey (bir nesne, bir fonksiyon vb.), o modülün ilk yüklendiği anda oluşturulur ve daha sonra bu modülü içe aktaran her yer aynı referansa sahip olur. Bu, herhangi bir ek kod yazmaya gerek kalmadan doğal bir Singleton davranışı sağlar.

Bir Singleton’ı ne zaman kullanmaktan kaçınmalıyım?

Singleton’dan kaçınmanız gereken durumlar şunlardır: eğer nesnenin birden fazla örneğine ihtiyaç duyulabilecekse, global durumun yönetimi karmaşıklığa yol açacaksa, test edilebilirliğin öncelikli olduğu durumlarda ve bağımlılık enjeksiyonu gibi daha esnek mimari desenler kullanılıyorsa. Genellikle, bir nesnenin tekil olmasının mimari bir gereklilikten ziyade sadece bir kolaylık olduğu durumlarda Singleton’dan kaçınılmalıdır.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.