Mundarija (31)
- Bu darsda
- 1. Nega bu kerak?
- 2. To'rt ustun
- 2.1 Inkapsulyatsiya — ichini yashirish
- 2.2 Abstraksiya — sodda tashqi ko'rinish
- 2.3 Meros — mavjud xulqni olish
- 2.4 Polimorfizm — bir chaqiruv, har xil xulq
- 3. Merosning narxi
- 3.1 Faqat bitta ota
- 3.2 Hamma narsa keladi
- 3.3 Mo'rt ota klass
- 4. Kompozitsiya: "ichida bor"
- 4.1 Obyektni ichida saqlash va ishni topshirish
- 4.2 Ikki tuzilma yonma-yon
- 4.3 Closure bilan kompozitsiya
- 5. Mixin: ko'nikma qo'shish
- 5.1 Object.assign bilan
- 5.2 Mixinning narxi
- 6. Qachon nima?
- 7. Ko'p uchraydigan xatolar
- 7.1 Kodni qayta ishlatish uchun meros
- 7.2 Ichki massivni tashqariga berib yuborish
- 7.3 Delegatsiyani unutish
- 8. Mashqlar
- 1-mashq (oson): To'rt ustunni toping
- 2-mashq (o'rta): Kompozitsiyaga aylantiring
- 3-mashq (qiyin): Kompozitsiya + polimorfizm
- 4-mashq: Vazifalar qadami — VazifalarRoyxati kompozitsiya bilan
- 9. Real ishda
- Xulosa
- Manbalar
OOP tamoyillari JavaScript'da: to'rt ustun va nega kompozitsiya merosdan ko'pincha yaxshiroq
Qisqacha: OOP — ma'lumot va uni o'zgartiradigan amallarni bitta obyektga yig'ib dasturlash. Uning to'rt ustuni: inkapsulyatsiya (ichini yashirish), abstraksiya (sodda tashqi ko'rinish), meros (
extends) va polimorfizm (bir xil chaqiruv — har xil xulq). Amalda eng muhim saboq: obyektda boshqa narsa "bor" bo'lsa — meros emas, kompozitsiya: kerakli obyektni ichida saqlang va ishni unga topshiring.
Bu darsda
- OOP ning to'rt ustunini «Bahor» misollarida tushuntira olasiz va ularni o'z kodingizda topa olasiz.
- Inkapsulyatsiya va abstraksiyaning farqini ayta olasiz.
- Polimorfizm va JavaScript'dagi duck typing ni ishlata olasiz.
- Kompozitsiya va mixin bilan merossiz qayta foydalanishni yoza olasiz.
vazifalaruchunVazifalarRoyxatiklassini kompozitsiya bilan yasaysiz.
Oldin bilishingiz kerak: Meros: extends va super, Getter/setter va static a'zolar, Private # maydon va metodlar, Closure amaliyotda.
1. Nega bu kerak?
Oxirgi besh darsda klass, maydon, #private, getter, static va merosni o'rgandingiz. Bular — asboblar. Endi savol boshqa: qachon qaysi asbobni olish kerak?
O'tgan darsdagi tajribani eslang. VazifalarRoyxati extends Array — qisqa va ishlaydigan kod edi. Lekin u ro'yxatga "salom" ni ham qo'shib yubordi, length = 0 bilan hamma vazifani o'chirib yubordi. Kod xato emas edi — qaror xato edi. Bunday qarorlarni to'g'ri qilish uchun dasturchilar o'nlab yillar davomida bir nechta tamoyil ishlab chiqqan.
Bu tamoyillar to'plami OOP — obyektga yo'naltirilgan dasturlash (object-oriented programming) deyiladi: dasturni ma'lumot va uning ustidagi amallarni birlashtirgan obyektlardan qurish. Siz uni allaqachon ishlatyapsiz — Buyurtma ham, Vazifa ham obyekt. Bugun ularga nom beramiz.
Ikkinchi sabab — ish. "OOP ning to'rt ustunini ayting" — junior intervyusining eng ko'p beriladigan savollaridan. Lekin yodlangan ta'rif yetmaydi. Suhbatdosh "misol keltiring" deydi. Bu darsdan keyin har ustunga o'z kodingizdan misol bera olasiz.
Hayotdan o'xshatish. Choyxona qurilishi. G'isht, sement, yog'och — bular asboblar (klass, #, extends). Ularni bilish usta bo'lish uchun yetmaydi. Qayerga devor, qayerga eshik qo'yish — bu loyihalash qoidalari. OOP tamoyillari — kodning loyihalash qoidalari.
2. To'rt ustun
2.1 Inkapsulyatsiya — ichini yashirish
Inkapsulyatsiya (encapsulation) — obyektning ichki ma'lumotini yashirib, unga faqat obyektning o'z metodlari orqali tegish (Private # maydon darsida tanishgansiz). Inglizcha "capsule" — "kapsula", "qobiq".
class Buyurtma {
#taomlar = [];
qosh(nom, narx) {
if (narx <= 0) {
return;
}
this.#taomlar.push({ nom, narx });
}
get jami() {
return this.#taomlar.reduce((s, t) => s + t.narx, 0);
}
}
const stol4 = new Buyurtma();
stol4.qosh("Osh", 35000);
stol4.qosh("Bepul choy", -1);
console.log(stol4.jami); // 35000#taomlar ga tashqaridan kirib bo'lmaydi (Private # maydon). Taom faqat qosh orqali qo'shiladi — va qosh qoidani tekshiradi. Natijada Buyurtma hech qachon "noto'g'ri" holatga tushmaydi. Bu qoida o'zgarmas shart (invariant) deyiladi: "buyurtmadagi har taom narxi musbat". Inkapsulyatsiya aynan invariantlarni himoya qilish uchun.
Hayotda: kassa apparati. Pul qutisi ichkarida, qulf bilan. Otabek pulga qo'l tegizmaydi — tugmalarni bosadi, apparat esa o'zi hisoblaydi va qutini ochadi.
2.2 Abstraksiya — sodda tashqi ko'rinish
Abstraksiya (abstraction) — murakkab ishni sodda nom ortiga yashirish: foydalanuvchi nima qilishini biladi, qanday qilinishini bilishi shart emas.
stol4.jami ni o'qigan kod summa qanday hisoblanishini bilmaydi. Ertaga Jasur aka "har chekka 10% xizmat haqi" desa, faqat jami getteri o'zgaradi. Uni ishlatgan o'nlab joy esa o'zgarmaydi — ular faqat jami degan nomni bilardi.
Hayotda: taksi ilovasi. Siz "Buyurtma berish" tugmasini bosasiz. Ichkarida — xarita, eng yaqin haydovchini qidirish, narx hisobi. Bularning hech biri ekranda yo'q. Sizga faqat bitta tugma kerak.
Inkapsulyatsiya va abstraksiya ko'pincha adashtiriladi, chunki ular yonma-yon yuradi. Farqi:
| Inkapsulyatsiya | Abstraksiya | |
|---|---|---|
| Savol | "Ichiga kim tega oladi?" | "Tashqaridan nima ko'rinadi?" |
| Maqsad | Himoya — invariant buzilmasin | Soddalik — keraksiz tafsilot yo'q |
| JS'da | #maydon, faqat metod orqali |
Aniq nomli metod va getterlar |
2.3 Meros — mavjud xulqni olish
Meros — o'tgan darsning mavzusi: class Ichimlik extends Taom. Bola klass ota klassning hamma xulqini oladi va farqini o'zi yozadi. To'rt ustunning eng mashhuri, lekin — bugun ko'ramiz — eng ehtiyot bilan ishlatiladigani.
Merosning asosiy va'dasi — qayta foydalanish: umumiy kod otada bir marta yoziladi. Bu va'da rost. Lekin qayta foydalanishning boshqa yo'llari ham bor, va ular ko'pincha arzonroq. Shuning uchun "bu kodni qayta ishlatmoqchiman" degan fikr merosga yetarli sabab emas. Sabab faqat bitta: "bola haqiqatan ham otaning bir turi".
2.4 Polimorfizm — bir chaqiruv, har xil xulq
Polimorfizm (polymorphism) — har xil turdagi obyektlar bir xil chaqiruvga o'z usulida javob berishi. Yunoncha "poly" — "ko'p", "morph" — "shakl".
O'tgan darsda buni ko'rgan edik: menyu.forEach((t) => console.log(t.tavsif())) — taom ham, ichimlik ham o'z tavsif ini bildi. JavaScript'da esa polimorfizm uchun umumiy ota klass ham shart emas:
class Taom {
constructor(nom, narx) {
this.nom = nom;
this.narx = narx;
}
tavsif() {
return `${this.nom}: ${this.narx} so'm`;
}
}
const aksiya = {
tavsif() {
return "Aksiya: birinchi ko'k choy bepul";
},
};
const ekran = [new Taom("Osh", 35000), aksiya];
ekran.forEach((x) => console.log(x.tavsif()));Konsolda:
Osh: 35000 so'm
Aksiya: birinchi ko'k choy bepulaksiya — klass ham emas, Taom ham emas. Oddiy obyekt literal. Lekin unda tavsif() bor — va forEach uchun shu yetarli. forEach callback'i "sen kimsan?" deb so'ramadi, faqat "tavsif qila olasanmi?" deb so'radi.
Bu uslub duck typing ("o'rdak testi") deyiladi: "o'rdakdek yursa va o'rdakdek g'ag'illasa — demak o'rdak". JavaScript obyektning turiga emas, unda kerakli metod borligiga qaraydi. Siz buni allaqachon ko'rgansiz: massivga o'xshash obyektlar length va indekslari bo'lgani uchun Array.from bilan ishlaydi.
Polimorfizmning foydasi — if'larning yo'qolishi. Usiz shunday yozardik:
if (x.turi === "taom") { ... }
else if (x.turi === "aksiya") { ... }
else if (x.turi === "ichimlik") { ... }Har yangi tur — yangi else if, va bunday zanjirlar kodning o'nta joyida. Polimorfizm bilan esa yangi tur faqat o'z tavsif ini olib keladi, mavjud kod o'zgarmaydi.
Tekshirib ko'ring:
ekranmassiviga{ nom: "Somsa" }obyekti qo'shilsa,forEachnima qiladi?
Javob
TypeError: x.tavsif is not a function. Duck typing ning narxi shu: "o'rdak" emas obyekt kelsa, xato faqat chaqiruv paytida chiqadi. Klasslarda bu xatoni kod yozish paytida ushlaydigan vosita — TypeScript (15-qism): u "bu massivdagi har element tavsif() ga ega bo'lsin" deb talab qila oladi.
3. Merosning narxi
To'rt ustun ichida meros eng ko'p muammo keltiradigani. Uchta sababini ko'ramiz.
3.1 Faqat bitta ota
«Bahor» xodimlari. Xodim — umumiy klass. Ofitsiant extends Xodim buyurtma oladi, Kassir extends Xodim to'lov oladi. Bir kuni kassir kasal bo'ldi va Otabek ikkalasini bajaryapti. class Otabek extends Ofitsiant, Kassir?
class Ofitsiant {}
class Kassir {}
class IkkiIsh extends Ofitsiant, Kassir {}SyntaxError: Unexpected token ','Tarjimasi: "kutilmagan belgi: vergul". extends dan keyin faqat bitta klass yozish mumkin. JavaScript'da ko'p ota (multiple inheritance) yo'q. Meros daraxtini oldindan to'g'ri chizsangiz ham, hayot uni buzadi — Otabekka ikki ota kerak bo'lib qoladi.
3.2 Hamma narsa keladi
O'tgan darsdagi tajriba: VazifalarRoyxati extends Array faqat filter va push ni emas, massivning 30 dan ortiq metodini oldi — splice, fill, length = 0 ham. Bizga ulardan uchtasi kerak edi, qolganlari esa qoidalarni chetlab o'tadigan eshiklar.
Meros "hammasi yoki hech narsa". Ota klassdan "faqat shu ikkitasini" olib bo'lmaydi.
3.3 Mo'rt ota klass
Ota klassdagi o'zgarish barcha bolalarga, hatto siz bilmagan bolalarga ham tarqaladi. Meros darsidagi "Ota konstruktori qayta yozilgan metodni chaqiradi" xatosini eslang: otaga bitta console.log qo'shildi — bolada undefined paydo bo'ldi. Bu muammo mo'rt ota klass (fragile base class) deyiladi. Zanjir qancha uzun bo'lsa, xavf shuncha katta.
Uchala muammoning ildizi bitta: meros — klasslar orasidagi eng mahkam bog'lanish. Bola otaning ichki tuzilishiga bog'lanib qoladi.
4. Kompozitsiya: "ichida bor"
4.1 Obyektni ichida saqlash va ishni topshirish
Kompozitsiya (composition) — obyekt boshqa obyektlarni ichida saqlaydi va ishning bir qismini ularga topshiradi. Meros "...ning bir turi" bo'lsa, kompozitsiya — "ichida ... bor".
class Savat {
#taomlar = [];
qosh(taom) {
this.#taomlar.push(taom);
}
tozala() {
this.#taomlar = [];
}
get jami() {
return this.#taomlar.reduce((s, t) => s + t.narx, 0);
}
}
class Buyurtma {
#savat = new Savat();
constructor(stol) {
this.stol = stol;
}
taomQosh(nom, narx) {
this.#savat.qosh({ nom, narx });
}
get jami() {
return this.#savat.jami;
}
}
const stol4 = new Buyurtma(4);
stol4.taomQosh("Lag'mon", 28000);
stol4.taomQosh("Ko'k choy", 5000);
console.log(stol4.jami); // 33000
console.log(stol4.tozala); // undefinedBuyurtma ichida Savat bor (#savat). stol4.jami ni o'qiganda Buyurtma ishni savatga topshiradi: return this.#savat.jami. Bu topshirish (delegation) deyiladi.
Endi oxirgi qatorga qarang: stol4.tozala — undefined. Savat da tozala bor, lekin Buyurtma uni tashqariga chiqarmadi. Ofitsiant chekni tasdiqlagandan keyin uni hech kim tozalab yubora olmaydi. Meros bilan bunday tanlov imkonsiz edi — extends Savat tozala ni ham olib kelardi.
Kompozitsiyada siz nimani tashqariga chiqarishni tanlaysiz. Ichki obyekt almashsa ham (masalan, savat boshqa klassga o'zgarsa), Buyurtma dan foydalangan kod o'zgarmaydi.
4.2 Ikki tuzilma yonma-yon
flowchart LR
subgraph M["Meros: bir turi"]
A1["Array<br/>30+ metod"] --> B1["VazifalarRoyxati<br/>hammasini oladi"]
end
subgraph K["Kompozitsiya: ichida bor"]
B2["VazifalarRoyxati<br/>qosh, ochir, top"] --> A2["#vazifalar<br/>(massiv)"]
end«Meros» qutisida strelka massivdan klassga: massivning hamma imkoniyati klassga oqib tushadi. «Kompozitsiya» qutisida strelka teskari — klass ichidagi massivga murojaat qiladi. Massiv yashirin, tashqariga faqat klass tanlagan uch-to'rt metod chiqadi.
1994-yilda nashr etilgan dizayn naqshlari haqidagi mashhur "To'rtlik kitobi" (Gang of Four) shunday tavsiya beradi: klass merosidan ko'ra obyekt kompozitsiyasini afzal ko'ring. Inglizchada bu "composition over inheritance" deb qisqa aytiladi. Bu taqiq emas — "avval kompozitsiyani o'ylab ko'r" degan maslahat.
4.3 Closure bilan kompozitsiya
Kompozitsiya uchun klass shart emas. Closure amaliyotda darsidagi fabrika funksiyalar ham obyektlarni birlashtira oladi:
function hisoblagichYasa() {
let soni = 0;
return {
oshir: () => soni++,
qiymat: () => soni,
};
}
function navbatYasa() {
const hisoblagich = hisoblagichYasa();
return {
yangiChek: () => {
hisoblagich.oshir();
return `Chek №${hisoblagich.qiymat()}`;
},
};
}
const kassa = navbatYasa();
kassa.yangiChek();
console.log(kassa.yangiChek()); // Chek №2navbatYasa ichida hisoblagich bor, tashqariga esa faqat yangiChek chiqdi. soni ni hech kim nolga tushira olmaydi. G'oya klassdagidek: ichki obyekt + topshirish + tanlangan tashqi ko'rinish.
Tekshirib ko'ring: «Bahor» uchun
Choyxonaklassi kerak: nomi, ish vaqti va stollar ro'yxati.Stolklassi tayyor.Choyxona extends StolyokiChoyxonaichida#stollar = [new Stol(...), ...]— qaysi biri?
Javob
Kompozitsiya: #stollar. "Choyxona — stolning bir turi" — noto'g'ri gap; "choyxonada stollar bor" — to'g'ri. Meros bilan choyxona bitta stolning maydonlarini (odam, band) olardi — bu ma'nosiz. Kompozitsiya bilan esa Choyxona ko'p stolni saqlaydi va boshStol() kabi metodlarni o'zi tanlab chiqaradi.
5. Mixin: ko'nikma qo'shish
5.1 Object.assign bilan
Otabek muammosiga qaytamiz: unga ham ofitsiant, ham kassir xulqi kerak, extends esa bitta. Yechimlardan biri — mixin: metodlar to'plamini (obyektni) klass prototipiga "aralashtirish". Inglizcha "mix in" — "ichiga qo'shib aralashtirmoq".
const BuyurtmaOlish = {
buyurtmaOl(taom) {
return `${this.ism}: ${taom} qabul qilindi`;
},
};
const TolovQabuli = {
tolovOl(summa) {
return `${this.ism}: ${summa} so'm olindi`;
},
};
class Xodim {
constructor(ism) {
this.ism = ism;
}
}
class Navbatchi extends Xodim {}
Object.assign(Navbatchi.prototype, BuyurtmaOlish, TolovQabuli);
const otabek = new Navbatchi("Otabek");
console.log(otabek.buyurtmaOl("Osh"));
console.log(otabek.tolovOl(40000));Konsolda:
Otabek: Osh qabul qilindi
Otabek: 40000 so'm olindiObject.assign ikki obyektning metodlarini Navbatchi.prototype ga ko'chirdi. Endi har navbatchi ikkala ko'nikmaga ega. this.ism ishlaydi, chunki metodlar otabek.buyurtmaOl(...) shaklida — nuqta bilan — chaqiriladi (this: to'rt qoida).
5.2 Mixinning narxi
Mixin qulay, lekin ikki tuzog'i bor:
- Nom to'qnashuvi. Ikki mixinda bir xil nomli metod bo'lsa,
Object.assignoxirgisini qoldiradi — jimgina, xato xabarisiz:
const Ofitsiant = {
salomlash() {
return "Xush kelibsiz! Menyu shu yerda.";
},
};
const Kassir = {
salomlash() {
return "Keyingi mijoz, marhamat.";
},
};
class Navbatchi {}
Object.assign(Navbatchi.prototype, Ofitsiant, Kassir);
console.log(new Navbatchi().salomlash()); // Keyingi mijoz, marhamat.Ofitsiantning salomi butunlay yo'qoldi. Mixinlar tartibini almashtirsangiz — kassirniki yo'qoladi. Ikkalasi ham kerak bo'lsa, mixin buni hal qila olmaydi: metodlarni boshqacha nomlash yoki kompozitsiyaga o'tish kerak (#ofitsiant.salomlash(), #kassir.salomlash()).
- Kelib chiqishi ko'rinmaydi.
otabek.tolovOlqayerdan keldi? Klassda yozilmagan, ota klassda ham yo'q. Kodni o'qigan odamObject.assignqatorini topmaguncha adashadi.
Yana bir nozik farq: Object.assign bilan qo'shilgan metodlar Object.keys(Navbatchi.prototype) da ko'rinadi, klass ichida yozilganlari esa yo'q. Sababi — class ichidan darsida.
Shuning uchun mixin — kichik, mustaqil ko'nikmalar uchun (masalan, "hodisa yubora oladi", "JSON'ga aylantira oladi"). Asosiy tuzilma uchun esa — kompozitsiya.
Maslahat: Kutubxonalarda mixinning boshqa shaklini ham uchratasiz — klass qaytaradigan funksiya:
const KassirBol = (Ota) => class extends Ota { ... }. Keyinclass Kassir extends KassirBol(Xodim) {}. Bu class expression va merosdan foydalanadi. Ishlash g'oyasi o'sha — xulqni qatlam-qatlam qo'shish.
6. Qachon nima?
| Munosabat | Tanlov | Misol |
|---|---|---|
| "...ning bir turi", zanjir sayoz | meros | Ichimlik extends Taom, X extends Error |
| "ichida ... bor" | kompozitsiya | Buyurtma ichida #savat |
| Mustaqil kichik ko'nikma | mixin yoki kompozitsiya | "hodisa yuboradi", "JSON'ga aylanadi" |
| Bir xil chaqiruv, har xil tur | polimorfizm | x.tavsif() |
Shubha bo'lsa — kompozitsiyadan boshlang. Kompozitsiyadan merosga o'tish oson: ichki obyektni ota klassga aylantirasiz. Teskarisi esa qiyin: meros bilan bog'langan kod otaning hamma metodiga tayanib qolgan bo'ladi.
Tekshirib ko'ring: Jasur aka "Bahor" uchun sodiqlik kartasi tizimini so'radi.
Mijozklassi bor. Karta egasi —KartaliMijoz extends MijozyokiMijozichida#karta? Mijoz kartasini yo'qotib, keyin yangisini olishi ham mumkin.
Javob
Kompozitsiya: #karta. Meros bilan "kartali" bo'lish nusxa yaratilganda bir marta hal bo'ladi — JavaScript'da mavjud nusxaning klassini keyin almashtirib bo'lmaydi. Kartani yo'qotgan mijoz uchun yangi Mijoz yaratib, hamma ma'lumotni ko'chirish kerak bo'lardi. Kompozitsiyada esa #karta ni null ga yoki yangi kartaga almashtirish yetarli. Vaqt o'tishi bilan o'zgaradigan narsa — kompozitsiyaning ishi.
7. Ko'p uchraydigan xatolar
7.1 Kodni qayta ishlatish uchun meros
"Choyxona da ham ish vaqti bor, Do'kon da ham — Do'kon extends Choyxona". Kod takrorlanmadi, lekin endi do'kon choyxonaning menyusini ham, stollarini ham oldi. Tuzatish: umumiy qismni alohida obyektga chiqaring (IshVaqti) va ikkalasi ichida saqlang.
7.2 Ichki massivni tashqariga berib yuborish
class Buyurtma {
#taomlar = ["Osh"];
get taomlar() {
return this.#taomlar;
}
}
const b = new Buyurtma();
b.taomlar.push("Bepul somsa");
console.log(b.taomlar); // [ 'Osh', 'Bepul somsa' ]#taomlar private edi, lekin getter o'sha massivning o'zini qaytardi (Havola semantikasi). Tashqi kod unga push qildi — inkapsulyatsiya buzildi, xato xabarisiz. Tuzatish: nusxa qaytaring — return [...this.#taomlar]; — yoki faqat kerakli natijani (get soni).
7.3 Delegatsiyani unutish
Kompozitsiyada ichki obyekt yangi metod olsa, tashqi klass uni avtomatik bermaydi. Bu xato emas — ataylab shunday. Lekin "nega stol4.tozala yo'q?" degan savol tug'ilsa, javob: tashqi klass uni chiqarmagan. Kerak bo'lsa — ongli ravishda qo'shing.
8. Mashqlar
1-mashq (oson): To'rt ustunni toping
Quyidagi koddagi har qatorni to'rt ustundan biriga bog'lang.
class Taom {
#narx; // A
constructor(nom, narx) {
this.nom = nom;
this.#narx = narx;
}
get narxMatni() { // B
return `${this.#narx} so'm`;
}
tavsif() {
return this.nom;
}
}
class Ichimlik extends Taom { // C
tavsif() { // D
return `${super.tavsif()} (ichimlik)`;
}
}Yechim
- A — inkapsulyatsiya: narx private, tashqaridan o'zgartirib bo'lmaydi.
- B — abstraksiya: tashqi kod narx qanday formatlanishini bilmaydi, faqat
narxMatnini o'qiydi. - C — meros:
Ichimlik— taomning bir turi. - D — polimorfizm:
x.tavsif()taomda ham, ichimlikda ham ishlaydi, lekin har xil natija beradi.
2-mashq (o'rta): Kompozitsiyaga aylantiring
Quyidagi kodda Stol Array dan meros oladi. Uni kompozitsiyaga o'tkazing: mehmonlar #mehmonlar da, tashqariga faqat otir(ism) (8 kishidan oshsa — qo'shmasin) va get soni chiqsin.
class Stol extends Array {
get soni() {
return this.length;
}
}Yechim
class Stol {
static SIGIM = 8;
#mehmonlar = [];
otir(ism) {
if (this.#mehmonlar.length >= Stol.SIGIM) {
console.warn(`Joy yo'q: ${ism}`);
return;
}
this.#mehmonlar.push(ism);
}
get soni() {
return this.#mehmonlar.length;
}
}
const stol = new Stol();
stol.otir("Malika");
stol.otir("Bobur");
console.log(stol.soni); // 2
console.log(stol.push); // undefinedstol.push — undefined: massiv metodlari endi tashqarida yo'q. Sig'im qoidasini chetlab o'tadigan eshik qolmadi.
3-mashq (qiyin): Kompozitsiya + polimorfizm
Chek klassini yozing. U ichida #qatorlar saqlasin — har qator tavsif() va get summa ga ega obyekt. qosh(qator), get jami va matn() metodi (har qator tavsifi yangi satrda, oxirida "Jami: N so'm"). Ikki xil qator klassi yozing: TaomQatori(nom, narx, soni) va ChegirmaQatori(foiz, chek) — chegirma summasi manfiy bo'lsin. Ishora: chegirma chekning chegirmagacha bo'lgan jamisidan hisoblanadi, shuning uchun u chekni ichida saqlashi kerak.
Yechim
class TaomQatori {
constructor(nom, narx, soni) {
this.nom = nom;
this.narx = narx;
this.soni = soni;
}
get summa() {
return this.narx * this.soni;
}
tavsif() {
return `${this.nom} x${this.soni}: ${this.summa}`;
}
}
class ChegirmaQatori {
#chek;
constructor(foiz, chek) {
this.foiz = foiz;
this.#chek = chek;
}
get summa() {
return -(this.#chek.taomlarJami * this.foiz) / 100;
}
tavsif() {
return `Chegirma ${this.foiz}%: ${this.summa}`;
}
}
class Chek {
#qatorlar = [];
qosh(qator) {
this.#qatorlar.push(qator);
}
get taomlarJami() {
return this.#qatorlar
.filter((q) => q instanceof TaomQatori)
.reduce((s, q) => s + q.summa, 0);
}
get jami() {
return this.#qatorlar.reduce((s, q) => s + q.summa, 0);
}
matn() {
const qatorlar = this.#qatorlar.map((q) => q.tavsif());
return [...qatorlar, `Jami: ${this.jami} so'm`].join("\n");
}
}
const chek = new Chek();
chek.qosh(new TaomQatori("Osh", 35000, 2));
chek.qosh(new TaomQatori("Ko'k choy", 5000, 2));
chek.qosh(new ChegirmaQatori(10, chek));
console.log(chek.matn());Konsolda:
Osh x2: 70000
Ko'k choy x2: 10000
Chegirma 10%: -8000
Jami: 72000 so'mIkki qator klassining umumiy otasi yo'q, lekin "shakli" bir xil: summa va tavsif(). Chekning jami va matn() metodlari ular orasida farq qilmaydi — bu polimorfizm (duck typing). Chegirma qatori chekni ichida saqlaydi — bu kompozitsiya.
Getter taomlarJami da instanceof bilan faqat taom qatorlari olindi. Busiz chegirma o'z summasini hisoblash uchun o'zini chaqirib, cheksiz aylanardi.
4-mashq: Vazifalar qadami — VazifalarRoyxati kompozitsiya bilan
refactor/vazifalar-royxati branch'ida davom etamiz. O'tgan darsdagi tajriba ko'rsatdi: massivdan meros — noto'g'ri. Endi to'g'ri yo'l.
Hozir vazifalar.js da vazifalar bilan ishlaydigan kod butun faylga sochilgan:
- ma'lumot:
holat.vazifalarmassivi (Vazifanusxalari) va globalyangiIdgeneratori (Closure amaliyotda); - so'rovlar: global
mosmi,korinadiganlar(filter+toSorted),statistika,holatdagiqoldigetter'i,vazifaniTop,xatoniTopichidagisome; - o'zgartirishlar:
vazifaQoshdagiyangiId()va yangi massiv,vazifaniOchirdagifilter,vazifaniAlmashtirvamatnniAlmashtirdagivazifaniTop(id)chaqiruvlari.
Ularning hammasi bitta ma'lumot — vazifalar to'plami — atrofida.
VazifalarRoyxati klassini yozing:
#vazifalar(Vazifalar massivi) va#yangiId(id generatori) — ichida, tashqaridan ko'rinmaydi.static dan(malumotlar)— oddiy obyektlar massividan royxat yasasin; generator eng kattaid+ 1 dan boshlansin.static mosmi(vazifa, filtr)— globalmosmishu yerga ko'chadi: unga nusxa kerak emas.statistika(),get qoldi,korinadiganlar(filtr),top(id),matnBormi(matn, ozId).- O'zgartiruvchilar:
qosh(matn),almashtir(id),matnniOzgartir(id, matn),ochir(id). Vazifaning o'zini o'zgartirish —Vazifaning metodlari (almashtir(),matnniOzgartir()) ishi; royxat ularni chaqiradi.
holat.vazifalar endi VazifalarRoyxati nusxasi bo'lsin, global yangiId esa olib tashlansin. Xulq o'zgarmasin.
Yechim
Klass — Vazifa dan keyin, faylning boshida:
class VazifalarRoyxati {
#vazifalar = [];
#yangiId = idYasovchiYarat(1);
static dan(malumotlar) {
const yangi = new VazifalarRoyxati();
yangi.#vazifalar = malumotlar.map((m) => Vazifa.dan(m));
const engKatta = malumotlar.reduce(
(eng, m) => Math.max(eng, m.id),
0,
);
yangi.#yangiId = idYasovchiYarat(engKatta + 1);
return yangi;
}
static mosmi(vazifa, filtr) {
if (filtr === "faol") {
return !vazifa.bajarildi;
}
if (filtr === "bajarilgan") {
return vazifa.bajarildi;
}
return true;
}
statistika() {
const guruhlar = Object.groupBy(this.#vazifalar, guruhNomi);
return {
qoldi: guruhlar.faol?.length ?? 0,
bajarildi: guruhlar.bajarilgan?.length ?? 0,
};
}
get qoldi() {
return this.statistika().qoldi;
}
korinadiganlar(filtr) {
return this.#vazifalar
.filter((v) => VazifalarRoyxati.mosmi(v, filtr))
.toSorted(faollarOldin);
}
top(id) {
return this.#vazifalar.find((v) => v.id === id);
}
matnBormi(matn, ozId) {
const kichik = matn.toLowerCase();
return this.#vazifalar.some(
(v) => v.id !== ozId && v.matn.toLowerCase() === kichik,
);
}
qosh(matn) {
const vazifa = new Vazifa(this.#yangiId(), matn);
this.#vazifalar.push(vazifa);
return vazifa;
}
almashtir(id) {
this.top(id).almashtir();
}
matnniOzgartir(id, matn) {
return this.top(id).matnniOzgartir(matn);
}
ochir(id) {
this.#vazifalar = this.#vazifalar.filter((v) => v.id !== id);
}
}statistika va korinadiganlar ning tanasi — global funksiyalardagi o'sha kod, faqat holat.vazifalar o'rniga this.#vazifalar. idYasovchiYarat, guruhNomi va faollarOldin global funksiya bo'lib qoladi — klass ularni chaqiradi.
Holat — qoldi getter'isiz: endi u royxatning o'zida (holat.vazifalar.qoldi). Uni sanalmaydigan qilgan Object.defineProperty(holat, "qoldi", …) qatori ham ketadi: klass getter'i prototipda turadi va o'zi sanalmaydi (class sintaksisi).
const holat = {
vazifalar: VazifalarRoyxati.dan(boshlangichVazifalar),
filtr: "hammasi",
tahrirId: null,
};Global funksiyalarning nomi va chaqiruvchilari o'zgarmaydi — faqat ichi royxatga topshiradi:
function xatoniTop(matn, vazifalar, ozId) {
if (matn === "") {
return "Vazifa matnini yozing.";
}
if (vazifalar.matnBormi(matn, ozId)) {
return "Bu vazifa ro'yxatda allaqachon bor.";
}
return null;
}
function korinadiganlar() {
return holat.vazifalar.korinadiganlar(holat.filtr);
}
function statistika() {
return holat.vazifalar.statistika();
}
function vazifaniTop(id) {
return holat.vazifalar.top(id);
}
function vazifaQosh(matn) {
holat.vazifalar.qosh(matn);
if (holat.filtr === "bajarilgan") {
holat.filtr = "hammasi";
}
render();
}
function vazifaniAlmashtir(id) {
holat.vazifalar.almashtir(id);
render();
}
function vazifaniOchir(id) {
holat.vazifalar.ochir(id);
render();
}
function matnniAlmashtir(id, matn) {
holat.vazifalar.matnniOzgartir(id, matn);
holat.tahrirId = null;
render();
}Global mosmi va const yangiId = idYasovchiYarat(4) o'chiriladi. render, hodisa tinglovchilari, fokus va e'lon kodi — bitta harf ham o'zgarmadi. Bu — abstraksiyaning amaldagi foydasi: ular faqat korinadiganlar(), statistika() kabi nomlarni bilardi.
Nimaga e'tibor bering:
- Static yordamchilar.
dan— factory (Getter/setter vastatic),mosmi— nusxaga bog'liq bo'lmagan predikat.static danichidayangi.#vazifalar = ...— boshqa nusxaning private maydoniga yozish klass ichida ruxsat etilgan. - Kompozitsiya ikki qavatli.
VazifalarRoyxatiichida massiv bor, massiv ichidaVazifalar bor. Royxat vazifaning ichiga tegmaydi —almashtir()vamatnniOzgartir()ni chaqiradi, ya'ni ishni yana topshiradi.matnniOzgartirningtrue/falsejavobi ham chaqiruvchiga o'tkaziladi. korinadiganlaryangi massiv qaytaradi (filter). «Ichki massivni tashqariga berib yuborish» xatosidan himoya: tashqi kod qaytgan massivgapushqilsa ham#vazifalaro'zgarmaydi.- ID generatori endi ichkarida. Closure amaliyotda darsidagi
idYasovchiYarato'zgarmadi — uning natijasi endi globalyangiIdemas, royxatning private#yangiIdmaydoni. Closure va klass birga ishlaydi: hisoblagich closure'da, closure esa#maydonda.danboshlang'ich sonni eng kattaiddan hisoblaydi — o'sha darsdagi qo'lda yozilgan4ham endi kerak emas.
O'tgan darsdagi tajriba savollari endi yopildimi — Node'da tekshiring. Bu blok faqat royxatning o'zini sinaydi (DOM yo'q, klasslar qisqartirilgan):
function idYasovchiYarat(boshlangich) {
let keyingi = boshlangich;
return () => keyingi++;
}
function guruhNomi(vazifa) {
return vazifa.bajarildi ? "bajarilgan" : "faol";
}
class Vazifa {
constructor(id, matn, bajarildi = false) {
this.id = id;
this.matn = matn;
this.bajarildi = bajarildi;
}
static dan(m) {
return new Vazifa(m.id, m.matn, m.bajarildi);
}
}
class VazifalarRoyxati {
#vazifalar = [];
#yangiId = idYasovchiYarat(1);
static dan(malumotlar) {
const yangi = new VazifalarRoyxati();
yangi.#vazifalar = malumotlar.map((m) => Vazifa.dan(m));
const engKatta = malumotlar.reduce(
(eng, m) => Math.max(eng, m.id),
0,
);
yangi.#yangiId = idYasovchiYarat(engKatta + 1);
return yangi;
}
statistika() {
const guruhlar = Object.groupBy(this.#vazifalar, guruhNomi);
return {
qoldi: guruhlar.faol?.length ?? 0,
bajarildi: guruhlar.bajarilgan?.length ?? 0,
};
}
get qoldi() {
return this.statistika().qoldi;
}
qosh(matn) {
const vazifa = new Vazifa(this.#yangiId(), matn);
this.#vazifalar.push(vazifa);
return vazifa;
}
}
const royxat = VazifalarRoyxati.dan([
{ id: 1, matn: "Non olish", bajarildi: false },
{ id: 3, matn: "Onamga qo'ng'iroq qilish", bajarildi: true },
]);
console.log(royxat.qosh("Choy olish").id); // 4
console.log(royxat.qosh("Kitob o'qish").id); // 5
console.log(royxat.statistika()); // { qoldi: 3, bajarildi: 1 }
console.log(royxat.qoldi); // 3
console.log(royxat.push, royxat.length); // undefined undefinedYangi vazifalar id si — 4 va 5: eng kattasi 3 edi, orada 2 bo'sh bo'lsa ham takror bo'lmaydi. push va length — undefined: o'tgan darsdagi uchala "begona" qator endi ishlamaydi.
Brauzerda ilovani sinab chiqing — ro'yxat bilan ishlash avvalgidek bo'lishi kerak:
- Ochilganda — uch vazifa, "2 ta vazifa qoldi, 1 ta bajarildi".
- Yangi vazifa qo'shing —
id— 4; yana bittasi — 5. Faol vazifalar ro'yxatda bajarilganlardan tepada, avvalgidek. - Vazifani belgilang va tahrirlang —
Vazifaning o'z metodlari royxat orqali ishlaydi. - Hammasini o'chirib, yangisini qo'shing —
id6 (Elements panelidadata-id): raqam qaytadan boshlanmaydi, 09-qismdagidek. - Takror matn ("non olish") — "Bu vazifa ro'yxatda allaqachon bor."; tahrirlash,
Esc, filtrlar, fokus — o'zgarishsiz.
Commit: Ro'yxat: VazifalarRoyxati klassiga ko'chir.
9. Real ishda
- Backend xizmatlari. NestJS (30-qism) va boshqa freymvorklarda xizmat klassi bazaga ulanish obyektini ichida saqlaydi va ishni unga topshiradi — kompozitsiya. Sinovda esa haqiqiy baza o'rniga soxtasi beriladi; meros bilan bu ancha qiyin bo'lardi.
- React. React komponentlari meros olmaydi — bir-birini ichiga joylab quriladi. React hujjatlari ham shuni tavsiya qiladi: komponentlarni qayta ishlatish uchun meros emas, kompozitsiya (17-qism).
- O'yinlar. O'yin dvigatellarida qahramon
Qahramon extends Jangchi extends Odamemas — "harakat", "sog'liq", "qurol" bo'laklaridan yig'iladi. Bu tuzilma "komponentlar tizimi" deb ataladi. - Intervyu. "OOP ning to'rt ustuni?", "inkapsulyatsiya va abstraksiya farqi?", "composition over inheritance nima?" — uchalasi ham deyarli har junior intervyusida. Javobingizni
BuyurtmavaVazifalarRoyxatimisollari bilan bersangiz — yodlangan ta'rifdan ancha kuchli.
Xulosa
- Inkapsulyatsiya — ichini yashirib, invariantni himoya qilish (
#, faqat metod orqali). - Abstraksiya — sodda nom ortida murakkablik; ichi o'zgarsa, ishlatuvchilar o'zgarmaydi.
- Meros — "bir turi" uchun, sayoz; bitta ota, hamma narsa keladi, ota o'zgarsa bolalar buziladi.
- Polimorfizm — bir chaqiruv, har xil xulq; JS'da umumiy ota shart emas (duck typing).
- Kompozitsiya — "ichida bor": obyektni saqlang, ishni topshiring, tashqariga faqat keraklisini chiqaring.
- Mixin — kichik ko'nikmalar uchun; nom to'qnashuvi va kelib chiqishning yashirinligidan ehtiyot bo'ling.
Keyingi dars: class ichidan: prototip ustidagi "shakar" — class aslida qanday prototip kodiga aylanishini va u konstruktor funksiyadan qaysi muhim joylarda farq qilishini ko'ramiz.
Manbalar
- MDN: "Classes", "Object-oriented programming", "Inheritance and the prototype chain" — developer.mozilla.org
- E. Gamma va boshq., "Design Patterns: Elements of Reusable Object-Oriented Software" (1994), 1-bob — "Inheritance versus Composition"
- React hujjatlari (eski sayt): "Composition vs Inheritance" — legacy.reactjs.org
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!