JAVASCRIPT // DESIGN PATTERNSD::04 İLERİ
24m READCOMPLETION: 75%ID::JS-301

JAVASCRIPT TASARIM DESENLERİ VE MİMARİ

Factory, Singleton, Decorator, Proxy, Observer ve Command desenleri

Tasarım desenleri, yazılım geliştirmede tekrar eden sorunlara kanıtlanmış çözümler sunar. JavaScript'te bu desenler özellikle modüler kod organizasyonu, async iş akışları ve nesne yönetimi için kritik öneme sahiptir.

Creational Patterns — Nesne Oluşturma

Factory Pattern

// JAVASCRIPT //
// Factory — nesne oluşturmayı soyutlar
class BildiriimYoneticisi {
  static olustur(tur, ayarlar) {
    const istemciler = {
      email:   () => new EmailIstemcisi(ayarlar),
      sms:     () => new SmsIstemcisi(ayarlar),
      webhook: () => new WebhookIstemcisi(ayarlar),
      slack:   () => new SlackIstemcisi(ayarlar),
    };
 
    const olusturucu = istemciler[tur];
    if (!olusturucu) throw new TypeError(`Bilinmeyen bildirim türü: ${tur}`);
 
    return olusturucu();
  }
}
 
// Kullanım — tur bilmek gerekmez
const bildirimci = BildiriimYoneticisi.olustur(process.env.NOTIFICATION_TYPE, config);
await bildirimci.gonder({ alici: 'ali@test.com', mesaj: 'Merhaba' });

Singleton Pattern

// JAVASCRIPT //
// Singleton — tek örnek garantisi
class VeritabaniHavuzu {
  static #ornek = null;
  #baglantilar = [];
 
  constructor(config) {
    if (VeritabaniHavuzu.#ornek) return VeritabaniHavuzu.#ornek;
    this.#baglantilar = Array(config.havuzBoyutu).fill(null).map(() => yeniBaglanti(config));
    VeritabaniHavuzu.#ornek = this;
  }
 
  baglanti() {
    return this.#baglantilar.find(b => !b.mesgul) ?? this.#yeniBaglanti();
  }
}
 
// Her yerde aynı örnek
const havuz1 = new VeritabaniHavuzu({ havuzBoyutu: 10 });
const havuz2 = new VeritabaniHavuzu({ havuzBoyutu: 5 });
console.log(havuz1 === havuz2); // true

Structural Patterns — Yapısal Desenler

Decorator Pattern

// JAVASCRIPT //
// Decorator — mevcut işlevselliği sarmalama
function zamanlayici(fn) {
  return async function(...args) {
    const baslangic = performance.now();
    const sonuc = await fn.apply(this, args);
    console.log(`${fn.name}: ${(performance.now() - baslangic).toFixed(2)}ms`);
    return sonuc;
  };
}
 
function onbellek(fn, gecerlilikMs = 60_000) {
  const kayitlar = new Map();
 
  return async function(...args) {
    const anahtar = JSON.stringify(args);
    const kayit = kayitlar.get(anahtar);
 
    if (kayit && Date.now() - kayit.zaman < gecerlilikMs) {
      return kayit.deger;
    }
 
    const deger = await fn.apply(this, args);
    kayitlar.set(anahtar, { deger, zaman: Date.now() });
    return deger;
  };
}
 
function yenidenDene(fn, denemeSayisi = 3, gecikme = 1000) {
  return async function(...args) {
    for (let i = 0; i < denemeSayisi; i++) {
      try {
        return await fn.apply(this, args);
      } catch (hata) {
        if (i === denemeSayisi - 1) throw hata;
        await new Promise(r => setTimeout(r, gecikme * Math.pow(2, i)));
      }
    }
  };
}
 
// Birleştirme — decorator zinciri
const kullaniciGetir = yenidenDene(onbellek(zamanlayici(async function kullaniciGetir(id) {
  const r = await fetch(`/api/kullanicilar/${id}`);
  return r.json();
})));

Proxy Pattern

// JAVASCRIPT //
// Proxy — nesne erişimini kontrol et
function dogrulamaProxy(nesne, kurallar) {
  return new Proxy(nesne, {
    set(hedef, ozellik, deger) {
      const kural = kurallar[ozellik];
      if (kural && !kural(deger)) {
        throw new TypeError(`${ozellik} için geçersiz değer: ${deger}`);
      }
      hedef[ozellik] = deger;
      return true;
    },
  });
}
 
const kullanici = dogrulamaProxy({}, {
  yas:   deger => Number.isInteger(deger) && deger >= 0 && deger <= 150,
  email: deger => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(deger),
});
 
kullanici.email = 'gecersiz'; // TypeError fırlatır
kullanici.email = 'ali@test.com'; // OK
 
// Reaktif state — Vue tarzı
function reaktif(veri, onChange) {
  return new Proxy(veri, {
    set(hedef, ozellik, deger) {
      const eskiDeger = hedef[ozellik];
      hedef[ozellik] = deger;
      if (eskiDeger !== deger) onChange(ozellik, deger, eskiDeger);
      return true;
    },
  });
}
 
const durum = reaktif({ sayac: 0 }, (key, yeni, eski) => {
  console.log(`${key}: ${eski}${yeni}`);
  guncelle(); // UI güncelle
});
 
durum.sayac++; // "sayac: 0 → 1"

Behavioral Patterns — Davranış Desenleri

Observer Pattern

// JAVASCRIPT //
// Observer / EventEmitter
class OlayYoneticisi {
  #dinleyiciler = new Map();
 
  on(olay, dinleyici) {
    if (!this.#dinleyiciler.has(olay)) {
      this.#dinleyiciler.set(olay, new Set());
    }
    this.#dinleyiciler.get(olay).add(dinleyici);
    return () => this.off(olay, dinleyici); // Abonelik iptali döndür
  }
 
  off(olay, dinleyici) {
    this.#dinleyiciler.get(olay)?.delete(dinleyici);
  }
 
  emit(olay, ...args) {
    this.#dinleyiciler.get(olay)?.forEach(d => {
      try { d(...args); } catch (e) { console.error(e); }
    });
  }
 
  once(olay, dinleyici) {
    const sarilmis = (...args) => {
      dinleyici(...args);
      this.off(olay, sarilmis);
    };
    return this.on(olay, sarilmis);
  }
}
 
// Kullanım
const olaylar = new OlayYoneticisi();
 
const abonelikten_cik = olaylar.on('kullanici:giris', kullanici => {
  console.log('Giriş yapıldı:', kullanici.ad);
});
 
olaylar.emit('kullanici:giris', { id: 1, ad: 'Ali' });
abonelikten_cik(); // Dinleyiciyi kaldır

Command Pattern

// JAVASCRIPT //
// Command — geri alınabilir işlemler
class MeninYoneticisi {
  #yapilan = [];
  #geriAlinan = [];
 
  uygula(komut) {
    komut.uygula();
    this.#yapilan.push(komut);
    this.#geriAlinan = []; // Yeni komut = redo sıfırla
  }
 
  geriAl() {
    const komut = this.#yapilan.pop();
    if (!komut) return;
    komut.geriAl();
    this.#geriAlinan.push(komut);
  }
 
  ilerle() {
    const komut = this.#geriAlinan.pop();
    if (!komut) return;
    komut.uygula();
    this.#yapilan.push(komut);
  }
}
 
// Somut komutlar
class MetinEkleKomut {
  constructor(editor, konum, metin) {
    this.editor = editor;
    this.konum = konum;
    this.metin = metin;
  }
  uygula()   { this.editor.ekle(this.konum, this.metin); }
  geriAl()   { this.editor.sil(this.konum, this.metin.length); }
}
 
// Kullanım
const yonetici = new MeninYoneticisi();
yonetici.uygula(new MetinEkleKomut(editor, 0, 'Merhaba'));
yonetici.uygula(new MetinEkleKomut(editor, 7, ' Dünya'));
yonetici.geriAl(); // ' Dünya' kaldırıldı
yonetici.ilerle(); // ' Dünya' geri eklendi

Strategy Pattern

// JAVASCRIPT //
// Strategy — algoritma ailesini değiştirilebilir yap
const siralama = {
  alfabetik: (a, b) => a.ad.localeCompare(b.ad, 'tr'),
  tarih:     (a, b) => new Date(b.tarih) - new Date(a.tarih),
  fiyat:     (a, b) => a.fiyat - b.fiyat,
  populerlik:(a, b) => b.goruntuleme - a.goruntuleme,
};
 
function urunleriSirala(urunler, strateji = 'alfabetik') {
  const siralamaFn = siralama[strateji];
  if (!siralamaFn) throw new Error(`Bilinmeyen strateji: ${strateji}`);
  return [...urunler].sort(siralamaFn);
}
 
// Validasyon stratejisi
const dogrulamalar = {
  zorunlu:  deger => deger !== null && deger !== undefined && deger !== '',
  email:    deger => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(deger),
  sayi:     deger => !isNaN(Number(deger)),
  minUzun:  min => deger => String(deger).length >= min,
};
 
function dogrula(deger, kurallar) {
  return kurallar.every(kural => {
    const fn = typeof kural === 'string' ? dogrulamalar[kural] : kural;
    return fn(deger);
  });
}
 
dogrula('ali@test.com', ['zorunlu', 'email']); // true
dogrula('', ['zorunlu', 'email']);              // false

Module Pattern ve IIFE

// JAVASCRIPT //
// Kapsülleme — private state
const SepetModulu = (() => {
  // Private
  let _urunler = [];
  let _indirim = 0;
 
  function _toplamHesapla() {
    return _urunler.reduce((t, u) => t + u.fiyat * u.adet, 0) * (1 - _indirim);
  }
 
  // Public API
  return {
    ekle(urun) {
      const mevcut = _urunler.find(u => u.id === urun.id);
      if (mevcut) { mevcut.adet++; } else { _urunler.push({ ...urun, adet: 1 }); }
    },
    cikart(id) {
      _urunler = _urunler.filter(u => u.id !== id);
    },
    indirimUygula(yuzde) {
      _indirim = yuzde / 100;
    },
    get toplam() { return _toplamHesapla(); },
    get urunler() { return [..._urunler]; }, // Kopya döndür
  };
})();
 
SepetModulu.ekle({ id: 1, ad: 'Laptop', fiyat: 15000 });
SepetModulu.indirimUygula(10);
console.log(SepetModulu.toplam); // 13500

Sonuç

Creational desenler nesne oluşturmayı soyutlar; Structural desenler bileşenleri birleştirir; Behavioral desenler iletişim ve algoritmaları düzenler. JavaScript'te bu desenleri; Factory ile tip bağımsızlığı, Decorator ile davranış katmanlama, Observer ile reaktif sistemler ve Command ile geri alınabilir işlemler için uygulayın. Bir sonraki derste TypeScript ile tip güvenli bu desenlerin nasıl yazılacağını inceleyeceğiz.