Series: JavaScript Basics Lesson 78

Design Patterns: Factory va Observer

Factory Pattern obyekt yaratish mantig'ini qanday ajratishi va JS'da first-class function tufayli nega tabiiyligi, Observer Pattern closure orqali qanday qurilishi, addEventListener aslida Observer ekani, Observer vs Pub/Sub farqi, va unsubscribe unutilsa memory leak yuzaga kelishi.

78-dars

Design Patterns: Factory va Observer

Oldingi mavzularda (ESM, CommonJS, Tree shaking) "V8 darajasi" — bu dvigatel ichida sodir bo'ladigan real mexanizm edi (parsing, memory, compilation). Design pattern'lar esa — V8'ning o'zi bilmaydigan, faqat dasturchilar orasida qabul qilingan kod yozish uslubi. V8 uchun Factory funksiya — bu shunchaki oddiy funksiya, Observer — bu shunchaki obyektlar massivi va metod chaqiruvlari. Hech qanday maxsus "pattern mexanizmi" dvigatelda yo'q.

Shuning uchun bu yerda "chuqurlashtirish" — V8 emas, balki qaysi JS til imkoniyatlari (closure, prototype, first-class function) ustiga bu patternlar qurilganini ko'rsatish bo'ladi.


1. Factory Pattern

new kalit so'zi bilan obyekt yaratish ba'zan noqulay yoki moslashuvchan emas:

class Car {
  constructor(type) {
    if (type === 'sedan') {
      this.wheels = 4;
      this.doors = 4;
    } else if (type === 'motorcycle') {
      this.wheels = 2;
      this.doors = 0;
    }
    // Bu constructor ichida shart-sharoit ko'payib ketsa - o'qish qiyinlashadi
  }
}

Factory Pattern — obyekt yaratish mantig'ini alohida funksiyaga chiqarib, chaqiruvchini "qaysi class kerakligi" haqidagi tafsilotlardan ozod qilish.

Oddiy misol

function createUser(role) {
  const base = { createdAt: Date.now() };

  switch (role) {
    case 'admin':
      return { ...base, role: 'admin', permissions: ['read', 'write', 'delete'] };
    case 'editor':
      return { ...base, role: 'editor', permissions: ['read', 'write'] };
    case 'viewer':
      return { ...base, role: 'viewer', permissions: ['read'] };
    default:
      throw new Error(`Noma'lum rol: ${role}`);
  }
}

const admin = createUser('admin');
const viewer = createUser('viewer');

Chaqiruvchi (createUser('admin')) qanday obyekt ichki tuzilishga ega bo'lishini bilishi shart emas — u faqat "menga admin kerak" deydi, qolgan tafsilot factory ichida yashiringan.

Class bilan Factory (real loyihalarda ko'proq uchraydigan)

class Admin {
  constructor() {
    this.permissions = ['read', 'write', 'delete'];
  }
}

class Viewer {
  constructor() {
    this.permissions = ['read'];
  }
}

class UserFactory {
  static create(role) {
    switch (role) {
      case 'admin': return new Admin();
      case 'viewer': return new Viewer();
      default: throw new Error("Noma'lum rol");
    }
  }
}

const user = UserFactory.create('admin');

Bu yerda UserFactory.create() — static method. new Admin() yoki new Viewer() chaqiruvi factory ichida yashiringan — tashqaridan hech kim to'g'ridan-to'g'ri new Admin() deb yozmaydi.

Nega bu foydali — asosiy sabab

  1. Constructor mantig'i murakkablashsa, uni bitta joyda (factory'da) boshqarish mumkin, new SomeClass() chaqiruvlari loyiha bo'ylab tarqalib ketmaydi.
  2. Test yozishda — factory'ni "mock" qilish oson (masalan, test paytida createUser funksiyasini almashtirib, haqiqiy obyekt o'rniga soxta obyekt qaytarish mumkin), lekin new User() chaqiruvlarini har joyda almashtirish qiyin.
  3. Qaysi class yaratilishi runtime'da hal qilinadi — masalan, konfiguratsiyaga yoki foydalanuvchi input'iga qarab turli class'lar yaratilishi kerak bo'lsa.

JS'da nega bu ayniqsa tabiiy — First-Class Function tufayli

Avval o'rgangan "funksiyalar birinchi darajali qiymat" tushunchasi tufayli, JS'da Factory Pattern boshqa (masalan, Java kabi) tillarga qaraganda ancha yengil — sizga alohida "Factory interfeysi" yoki murakkab class ierarxiyasi kerak emas, oddiy funksiya obyekt qaytarsa bo'ldi, bu allaqachon Factory.


2. Observer Pattern

Ba'zan bitta obyektdagi o'zgarish boshqa ko'plab, bir-biridan bexabar qismlarga xabar berilishi kerak, lekin ular orasida qattiq bog'liqlik (tight coupling) bo'lmasligi kerak:

// YOMON - qattiq bog'liqlik
function updateTemperature(newTemp) {
  temperature = newTemp;
  updateDisplay(newTemp);      // display'ni bilishi SHART
  logToServer(newTemp);        // logger'ni bilishi SHART
  checkAlarmThreshold(newTemp); // alarm'ni bilishi SHART
  // Yangi funksiya qo'shilsa - shu yerni har safar o'zgartirish kerak
}

Observer Pattern — bitta obyekt ("Subject"/"Publisher") holatini kuzatib turadigan ko'plab boshqa obyektlarga ("Observers"/"Subscribers") o'zgarish haqida avtomatik xabar berish mexanizmi, lekin Subject ularning kim ekanligini yoki nima qilishini bilmaydi.

Implementatsiya — closure va massiv orqali

function createSubject() {
  const observers = []; // private - closure orqali yashiringan

  return {
    subscribe(fn) {
      observers.push(fn);
    },
    unsubscribe(fn) {
      const index = observers.indexOf(fn);
      if (index !== -1) observers.splice(index, 1);
    },
    notify(data) {
      observers.forEach(fn => fn(data)); // hammasiga xabar
    }
  };
}

const temperatureSubject = createSubject();

// Har xil qismlar, bir-biridan mustaqil, ro'yxatdan o'tadi
temperatureSubject.subscribe((temp) => console.log(`Display: ${temp}°C`));
temperatureSubject.subscribe((temp) => console.log(`Log serverga: ${temp}`));
temperatureSubject.subscribe((temp) => {
  if (temp > 40) console.log('⚠️ ALARM!');
});

temperatureSubject.notify(45);
// Display: 45°C
// Log serverga: 45
// ⚠️ ALARM!

Diqqat qiling: createSubject — bu aynan Module Pattern'ning o'zi (closure orqali observers massivini private saqlash) — ikkita pattern shu yerda tabiiy ravishda birlashib ketyapti. Bu — nega patternlarni alohida-alohida yodlash emas, balki qaysi til xususiyati asosida qurilganini tushunish muhimligiga yaxshi misol.

Real hayotdagi tanish misol — bu yangi narsa emas!

Aslida siz Observer Pattern'ni allaqachon har kuni ishlatgansiz:

button.addEventListener('click', () => console.log('Bosildi 1'));
button.addEventListener('click', () => console.log('Bosildi 2'));

button.click();
// Bosildi 1
// Bosildi 2

addEventListener — bu aynan subscribening o'zi! button — Subject, ro'yxatdan o'tgan callback'lar — Observers. Brauzer DOM API'si ichki tuzilishi aynan Observer Pattern asosida qurilgan (Event bubbling/delegationning tag-tugida shu mantiq yotadi).

Muhim farq: Observer vs Pub/Sub

Bu yerda kichik terminologik nozik nuqtani hozirdanoq aytib qo'yaylik, chunki keyingi mavzu aynan shu:

  • Observer pattern (klassik) — Subject observerlarni to'g'ridan-to'g'ri biladi (ularga referens saqlaydi, observers massivi orqali), va ular odatda bir xil "context"da ishlaydi.
  • Pub/Sub — orada qo'shimcha "Event Bus" / "Broker" qatlami bor, Publisher va Subscriber bir-birini umuman bilmaydi, faqat umumiy "event nomi" orqali bog'lanadi.

Yuqoridagi createSubject misoli — texnik jihatdan Observerga yaqinroq (chunki subscribe to'g'ridan-to'g'ri o'sha bitta subject'ga bog'langan). Pub/Sub'da esa kod butunlay boshqacha ko'rinadi — buni keyingi mavzuda to'liq ko'ramiz.


3. Xotira boshqaruvi bilan bog'liqlik

Observer Pattern'ning eng ko'p uchraydigan xatosi — bu memory leak:

class Component {
  constructor(subject) {
    this.subject = subject;
    this.handler = (data) => this.render(data);
    subject.subscribe(this.handler);
  }

  destroy() {
    // Agar bu qatorni UNUTSANGIZ:
    // this.subject.unsubscribe(this.handler);
    //
    // Component "o'chgan" deb hisoblansa ham, subject uning
    // handler'ini hali ushlab turibdi -> GC uni tozalay olmaydi!
  }
}

Agar unsubscribe chaqirilmasa, Subject'ning observers massivi component'ning handler funksiyasiga referens saqlab qoladi. Reachability qoidasiga ko'ra — agar biror narsaga root'dan yetib borish mumkin bo'lsa, GC uni tozalay olmaydi. Bu yerda subject (odatda uzoq umr ko'radigan, masalan global) — root'ga yaqin, va u orqali "o'lgan" component'ning handler'iga hali ham yo'l bor, shuning uchun butun component xotirada qolib ketadi, garchi u DOM'dan olib tashlangan bo'lsa ham.