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
- Constructor mantig'i murakkablashsa, uni bitta joyda (factory'da) boshqarish mumkin,
new SomeClass()chaqiruvlari loyiha bo'ylab tarqalib ketmaydi. - Test yozishda — factory'ni "mock" qilish oson (masalan, test paytida
createUserfunksiyasini almashtirib, haqiqiy obyekt o'rniga soxta obyekt qaytarish mumkin), lekinnew User()chaqiruvlarini har joyda almashtirish qiyin. - 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,
observersmassivi 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.