Design Patterns — Singleton, Factory, Observer, Strategy

~30 daq · JavaScript · Amaliy va Professional mavzular

Muammodan boshlaylik

To'lov funksiyasi:

function tolovQil(usul, summa) {
  if (usul === 'click') {
    // 30 qator Click logikasi
  } else if (usul === 'payme') {
    // 30 qator Payme logikasi
  } else if (usul === 'uzum') {
    // 30 qator Uzum logikasi
  }
  // yangi usul — yana bitta else if...
}

Har bir yangi to'lov usuli — eski funksiyani o'zgartirish va hamma narsani qayta test qilish. Bunday muammolar ming marta uchragan va ularning sinalgan yechimlari bor — design pattern lar.


Design pattern nima

Design pattern — tez-tez uchraydigan muammoning qayta ishlatiladigan yechim shabloni. Bu tayyor kod emas, balki g'oya: "bunday vaziyatda kodni shunday tuzing". 1994-yilda "Gang of Four" kitobida 23 ta pattern tasvirlangan; JavaScript da ularning ko'pi soddaroq ko'rinishga ega, chunki funksiyalar — first-class.

Bu darsda eng ko'p ishlatiladigan to'rttasi.


Singleton — yagona nusxa

Muammo: ilovada faqat bitta nusxa bo'lishi kerak: sozlamalar, API klient, ma'lumotlar bazasi ulanishi.

JavaScript da ES modul o'zi singleton — modul faqat bir marta bajariladi:

// src/api.js
class ApiKlient {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
    this.token = null;
  }

  async get(yol) {
    const res = await fetch(this.baseUrl + yol, {
      headers: this.token ? { Authorization: `Bearer ${this.token}` } : {},
    });
    return res.json();
  }
}

export const api = new ApiKlient(import.meta.env.VITE_API_URL);
// istalgan fayl
import { api } from './api.js';   // har doim o'sha bitta nusxa

Klassik ko'rinish:

class Sozlamalar {
  static #nusxa = null;

  static olish() {
    if (!Sozlamalar.#nusxa) Sozlamalar.#nusxa = new Sozlamalar();
    return Sozlamalar.#nusxa;
  }
}

Sozlamalar.olish() === Sozlamalar.olish(); // true

[!WARNING] Singleton — yashirin global holat. Uni testlashda almashtirish qiyin va hamma joydan o'zgartirilishi mumkin. Faqat haqiqatan yagona bo'lishi kerak bo'lgan resurslar uchun ishlating.


Factory — obyekt yaratishni markazlashtirish

Muammo: turga qarab har xil obyekt yaratish kerak, va bu mantiq kod bo'ylab tarqalib ketgan.

class Matn { constructor(o) { this.o = o; } chiz() { return `<input type="text" name="${this.o.nom}">`; } }
class Raqam { constructor(o) { this.o = o; } chiz() { return `<input type="number" name="${this.o.nom}">`; } }
class Tanlov {
  constructor(o) { this.o = o; }
  chiz() {
    const opts = this.o.variantlar.map((v) => `<option>${v}</option>`).join('');
    return `<select name="${this.o.nom}">${opts}</select>`;
  }
}

const turlar = { matn: Matn, raqam: Raqam, tanlov: Tanlov };

function maydonYarat(sozlama) {
  const Klass = turlar[sozlama.tur];
  if (!Klass) throw new Error(`Noma'lum maydon turi: ${sozlama.tur}`);
  return new Klass(sozlama);
}
const forma = [
  { tur: 'matn', nom: 'ism' },
  { tur: 'raqam', nom: 'yosh' },
  { tur: 'tanlov', nom: 'shahar', variantlar: ['Toshkent', 'Buxoro'] },
];

const html = forma.map((s) => maydonYarat(s).chiz()).join('');

Chaqiruvchi qaysi klass yaratilishini bilmaydi — faqat maydonYarat ni. Yangi tur — turlar ga bitta qator.

[!INFO] JavaScript da factory ko'pincha oddiy funksiya: function createUser(ism) { return { ism, salom() {...} }; } — new va klasslarsiz obyekt yaratish ham "factory function" deyiladi.


Observer — obuna bo'lish

Muammo: bitta obyekt o'zgarganda boshqa ko'p qismlar xabardor bo'lishi kerak, lekin ular bir-biriga bog'lanmasligi kerak.

class EventEmitter {
  #tinglovchilar = new Map();

  on(hodisa, fn) {
    if (!this.#tinglovchilar.has(hodisa)) this.#tinglovchilar.set(hodisa, new Set());
    this.#tinglovchilar.get(hodisa).add(fn);
    return () => this.off(hodisa, fn);
  }

  off(hodisa, fn) {
    this.#tinglovchilar.get(hodisa)?.delete(fn);
  }

  emit(hodisa, ...args) {
    this.#tinglovchilar.get(hodisa)?.forEach((fn) => fn(...args));
  }
}
const hodisalar = new EventEmitter();

hodisalar.on('buyurtma:yaratildi', (b) => emailYubor(b.email));
hodisalar.on('buyurtma:yaratildi', (b) => analitika('purchase', b.summa));
hodisalar.on('buyurtma:yaratildi', () => savatniTozala());

// buyurtma moduli boshqalar haqida bilmaydi
hodisalar.emit('buyurtma:yaratildi', buyurtma);

Siz Observer ni har kuni ishlatasiz: addEventListener, Promise .then, 79-darsdagi store subscribe.


Strategy — almashtiriladigan algoritmlar

Muammo: bir vazifaning bir nechta usuli bor va ular if/else zanjirida.

Dars boshidagi to'lov misoli:

const tolovStrategiyalari = {
  click: {
    async tolov(summa) { /* Click API */ },
    komissiya: (summa) => summa * 0.01,
  },
  payme: {
    async tolov(summa) { /* Payme API */ },
    komissiya: (summa) => summa * 0.015,
  },
  naqd: {
    async tolov() { return { holat: 'kutilmoqda' }; },
    komissiya: () => 0,
  },
};

async function tolovQil(usul, summa) {
  const strategiya = tolovStrategiyalari[usul];
  if (!strategiya) throw new Error(`Qo'llab-quvvatlanmaydigan usul: ${usul}`);
  const jami = summa + strategiya.komissiya(summa);
  return strategiya.tolov(jami);
}

Yangi usul — yangi obyekt; tolovQil o'zgarmaydi. Boshqa misollar: tartiblash usullari, validatsiya qoidalari, narx hisoblash (chegirma turlari).

const tartiblar = {
  arzon: (a, b) => a.narx - b.narx,
  qimmat: (a, b) => b.narx - a.narx,
  yangi: (a, b) => b.sana - a.sana,
};

mahsulotlar.toSorted(tartiblar[tanlangan]);

Qisqacha

Pattern Muammo JS dagi ko'rinishi
Singleton Yagona nusxa Modul eksporti
Factory Turga qarab yaratish Funksiya + turlar xaritasi
Observer O'zgarish haqida xabar on/emit, addEventListener, subscribe
Strategy Almashtiriladigan algoritm Funksiyalar obyekti

[!TIP] Pattern ni pattern uchun ishlatmang. Ikki shartli if/else — Strategy talab qilmaydi. Pattern kod murakkablashib, takrorlanish yoki o'zgartirish qiyinligi sezilganda kerak.


Nima uchun shunday?

Nima uchun pattern larni o'rganish kerak — har bir muammoni o'zim hal qila olmaymanmi?

Hal qila olasiz, lekin pattern lar ikki narsa beradi. Birinchidan, sinalgan yechim: minglab dasturchilar bu muammolarga duch kelgan va bu yechimlarning qayerda ishlashi va qayerda muammo tug'dirishi ma'lum. Ikkinchidan, umumiy til: "bu yerda Strategy ishlatamiz" degan bitta jumla jamoadoshga butun arxitekturani tushuntiradi. Pattern larning umumiy g'oyasi — o'zgaradigan narsani o'zgarmaydigandan ajratish: yangi to'lov usuli, yangi maydon turi yoki yangi tinglovchi qo'shilganda eski, sinalgan kod tegilmay qoladi.


Xulosa

Eslab qoling:

  • Design pattern — tez-tez uchraydigan muammoning sinalgan yechim shabloni
  • Singleton — yagona nusxa; JS da modul eksporti; ehtiyotkorlik bilan
  • Factory — yaratish mantiqini bir joyga yig'ish; turlar xaritasi
  • Observer — on/off/emit; obunachilar bir-biriga bog'lanmaydi
  • Strategy — if/else o'rniga almashtiriladigan funksiyalar obyekti
  • Pattern ni faqat haqiqiy muammo bo'lganda ishlating

Keyingi dars

81-dars: Testing — Jest bilan JavaScript testlash