9.1-bob: SOLID prinsiplari
9-QISM — Arxitektura va ilg'or backend · 1-mavzu
1. Kirish va motivatsiya
8-QISM (NestJS) bilan backend texnologiyalarini chuqur o'rgandik. Endi 9-QISM'da boshqa darajaga — kodni qanday to'g'ri tashkil qilish (arxitektura) — o'tamiz. Texnologiyani bilish (NestJS, DB) — bir narsa; uni toza, kengaytiriladigan, jamoaviy qilib yozish — boshqa narsa. Bu — junior va senior dasturchini eng aniq ajratadigan farq. 9-QISM'ning birinchi va eng muhim mavzusi — SOLID — beshta tamoyildan iborat, ular "chalkash, mo'rt kod"ni "o'qiladigan, test qilinadigan, oson o'zgaradigan" kodga aylantiradi.
SOLID — Robert Martin (Uncle Bob) ommalashtirgan 5 ta obyektga yo'naltirilgan dizayn tamoyili: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion. Bular alohida qoidalar emas — birgalikda moslashuvchan arxitektura falsafasini tashkil qiladi. Eng muhimi: siz bularni allaqachon NestJS'da amalda ishlatgansiz (DI — 8.2 — bu DIP; service ajratish — bu SRP; provider abstraksiya — bu OCP). Endi ularning nomini, sababini, qoidasini bilib olasiz.
Bu bob — promptdagi "har biri misol bilan" talabini bajaradi: har tamoyilni yomon yaxshi kod bilan, nega kerakligini, real loyihada (e-commerce, NestJS) qanday qo'llashni ko'ramiz. SOLID — kod sifatining poydevori (15-QISM clean code, 8.11 test bilan bog'liq). Bu bob: SRP, OCP, LSP, ISP, DIP — chuqur, TypeScript misollar bilan. SOLID — har senior dasturchi tilidagi atama (intervyu, code review).
O'xshatish: SOLID — bino qurish me'yorlari (qurilish standartlari). Bino (ilova) texnik jihatdan turishi mumkin (kod ishlaydi), lekin me'yorsiz qurilgan bino — kengaytirib bo'lmaydi (yangi qavat qo'shsangiz yiqiladi), ta'mirlash qiyin (bir devorga teginsangiz, boshqasi buziladi), zilzilaga chidamsiz (bir o'zgarish hammasini sindiradi). SOLID me'yorlari — har xona bitta vazifa (SRP), kengaytirishga tayyor (OCP), qismlar almashtiriladigan (LSP), interfeys aniq (ISP), poydevor mustahkam abstraksiyaga tayanadi (DIP). Me'yorli bino — yillar turadi, oson kengayadi.
Nega muhim?
- Junior senior farqi — texnologiya emas, kod tashkili.
- Kengaytiriladigan — yangi xususiyat eski kodni sindirmaydi.
- Test qilinadigan — DIP mock'ni oson qiladi 8.11-bob.
- NestJS asosi — DI, modul, provider — SOLID amalda.
2. Nazariya — chuqur tushuntirish
2.1. SOLID umumiy ko'rinish
S — Single Responsibility — bitta klass, bitta vazifa (bitta o'zgarish sababi)
O — Open/Closed — kengaytirishga ochiq, o'zgartirishga yopiq
L — Liskov Substitution — ota o'rniga bola qo'yilsa, buzilmasin
I — Interface Segregation — kichik, maxsus interfeyslar (katta umumiy emas)
D — Dependency Inversion — abstraksiyaga tayaning (konkretga emas)
Maqsad: mo'rt, chalkash kod moslashuvchan, test qilinadigan, oson o'zgaradiganSOLID — 5 ta dizayn tamoyili (Uncle Bob). Ular birgalikda: o'zgarishga chidamli (bir joy o'zgarsa, boshqasi tegmaydi), kengaytiriladigan (yangi kod qo'shiladi, eski o'zgarmaydi), test qilinadigan (DIP — mock). Har biri keyingi bo'limlarda chuqur. Bular — OOP (2.10, 7) dizaynining asosi.
2.2. S — Single Responsibility Principle (SRP)
Bitta klassning o'zgarishi uchun faqat bitta sabab bo'lishi kerak.
// YOMON — User klassi ko'p vazifa (3 sabab o'zgaradi)
class User {
constructor(public ism: string, public email: string) {}
saqla() { /* DB mantiq */ } // 1. DB o'zgarsa
emailYubor() { /* SMTP mantiq */ } // 2. Email o'zgarsa
validatsiya() { /* tekshiruv */ } // 3. Qoida o'zgarsa
}
// YAXSHI — har klass bitta vazifa
class User { // faqat ma'lumot
constructor(public ism: string, public email: string) {}
}
class UserRepository { saqla(user: User) {} } // faqat DB (8.3)
class EmailService { yubor(user: User) {} } // faqat email (8.10)
class UserValidator { tekshir(user: User) {} } // faqat validatsiya (8.5)SRP — bir klass — bir vazifa (bir o'zgarish sababi). Yomon misolda
Userham ma'lumot, ham DB, ham email, ham validatsiya — har biri alohida sabab bilan o'zgaradi (chalkashlik). Yaxshi — ajratilgan (NestJS: controller/service/repository — 8.1: 2.7 — bu SRP!). Foyda: o'zgarish lokal (email o'zgarsa, DB tegmaydi), test oson.
2.3. SRP — chuqurroq (nega muhim)
SRP buzilishi belgilari:
- Klass nomida "And"/"Manager"/"Util" (ko'p vazifa)
- Klass juda katta (500+ qator)
- Bir o'zgarish ko'p joyni buzadi
- Test qilish qiyin (ko'p narsani mock kerak)
SRP foydasi:
O'zgarish izolyatsiyasi (bir vazifa o'zgarsa, boshqasi tegmaydi)
Qayta ishlatish (EmailService boshqa joyda ham)
Test oson (bitta vazifa — bitta test)
Jamoa (turli odam turli klass — to'qnashuv kam)SRP chuqurroq: "vazifa" = "o'zgarish sababi" (kim/nima uchun bu kod o'zgaradi). Agar klass turli sabablar (DB, email, biznes qoida) uchun o'zgarsa — buzilgan. Belgi: "Manager"/"Util" nomlar, katta klass. Foyda: izolyatsiya, qayta ishlatish, test. NestJS modulli arxitektura 8.1-bob — SRP'ni tabiiy qo'llaydi.
2.4. O — Open/Closed Principle (OCP)
Kod kengaytirishga ochiq, o'zgartirishga yopiq bo'lishi kerak.
// YOMON — yangi to'lov turi mavjud kodni o'zgartirish (if qo'shish)
class TolovService {
tolov(tur: string, summa: number) {
if (tur === "click") { /* Click */ }
else if (tur === "payme") { /* Payme */ }
else if (tur === "uzum") { /* yangi BU KODNI o'zgartirish kerak! */ }
}
}
// YAXSHI — interfeys + yangi klass (mavjud kod o'zgarmaydi)
interface TolovUsuli {
tolov(summa: number): Promise<void>;
}
class ClickTolov implements TolovUsuli { async tolov(s: number) {} }
class PaymeTolov implements TolovUsuli { async tolov(s: number) {} }
class UzumTolov implements TolovUsuli { async tolov(s: number) {} } // YANGI — eski tegmaydi!
class TolovService {
async tolov(usul: TolovUsuli, summa: number) { // abstraksiyaga tayanadi
await usul.tolov(summa);
}
}OCP — yangi xulq qo'shganda mavjud kodni o'zgartirmaslik (faqat qo'shish). Yomon: yangi to'lov
ifqo'shish (mavjud kod o'zgaradi xato xavfi, qayta test). Yaxshi: interfeys + yangi klass (UzumTolovqo'shiladi, eski tegmaydi). Strategy pattern 9.2-bob — OCP amalda. Foyda: yangi xususiyat xavfsiz (eski kod sinmaydi).
2.5. OCP — abstraksiya kuchi
OCP qanday erishiladi:
- Interfeys/abstrakt klass (kengaytirish nuqtasi)
- Polimorfizm (2.10 — turli implementatsiya)
- Strategy/Factory pattern 9.2-bob
- Dependency Injection (8.2 — provider almashtirish)
Hamma narsani OCP qilmang (YAGNI — kerak bo'lganda)
o'zgarish ehtimoli yuqori joyda (to'lov, bildirishnoma turlari)OCP erishish: interfeys + polimorfizm 2.10-bob + DI 8.2-bob. Yangi implementatsiya qo'shiladi, mavjud kod yopiq. Lekin hamma narsani OCP qilmang (YAGNI — "kerak bo'lmaydi" — ortiqcha abstraksiya ham yomon). O'zgarish ehtimoli yuqori joyga (to'lov usullari, bildirishnoma kanallari) qo'llang. Balans muhim.
2.6. L — Liskov Substitution Principle (LSP)
Ota klass o'rniga bola klass qo'yilsa, dastur to'g'ri ishlashda davom etishi kerak.
// YOMON — bola ota shartnomasini buzadi
class Qush {
ucha() { return "uchyapti"; }
}
class Pingvin extends Qush {
ucha() { throw new Error("Pingvin ucholmaydi!"); } // shartnoma buzildi!
}
function uchir(qush: Qush) { qush.ucha(); } // Pingvin xato!
// YAXSHI — to'g'ri ierarxiya
class Qush {}
class UchadiganQush extends Qush { ucha() {} }
class Pingvin extends Qush { suzadi() {} } // ucha() yo'q (to'g'ri model)LSP — bola ota o'rniga muammosiz ishlashi kerak (ota shartnomasini buzmasligi). Yomon:
PingvinQush'dan meros, lekinucha()ishlamaydi (shartnoma buzilgan — kutilmagan xato). Yaxshi: to'g'ri ierarxiya (uchadigan/uchmaydigan ajratilgan). LSP buzilishi — noto'g'ri meros (is-a aloqasi noto'g'ri). Polimorfizm ishonchli bo'lishi uchun.
2.7. LSP — amaliy ma'no
LSP buzilish belgilari:
- Bola metodi xato tashlaydi (ota qilmaydigan)
- Bola metodi bo'sh (hech narsa qilmaydi)
- Bolada "instanceof" tekshiruvi kerak bo'ladi
- Bola ota shartlarini kuchaytiradi (qattiqroq talab)
Qoida: meros (extends) — faqat haqiqiy "is-a" bo'lsa
shubha bo'lsa, kompozitsiya (has-a) afzal (2.10)LSP amaliy: belgilar — bola xato tashlaydi/bo'sh metod/
instanceofkerak. Sababi — noto'g'ri meros (is-a emas). Qoida: meros faqat haqiqiy is-a (Pingvin "is-a" Qush, lekin uchmaydi model noto'g'ri). Shubhada — kompozitsiya (has-a — 2.10 — "composition over inheritance"). LSP — meros to'g'ri ishlatilishini ta'minlaydi.
2.7a. LSP — shartnoma qoidalari (precondition/postcondition/invariant)
Barbara Liskov'ning asl ta'rifi rasmiy shartnoma (contract) tushunchasiga tayanadi. Bola klass ota o'rniga to'g'ri ishlashi uchun uchta qoidaga rioya qilishi shart:
1) PRECONDITION (kirish sharti) KUCHAYTIRILMASLIGI kerak
bola ota qabul qilgandan KO'PROQ talab qo'yolmaydi
ota: har qanday son qabul qiladi; bola: faqat musbat son (kuchaytirdi)
2) POSTCONDITION (chiqish sharti) SUSAYTIRILMASLIGI kerak
bola ota va'da qilgandan KAMROQ narsa qaytarolmaydi
ota: musbat natija va'da qiladi; bola: manfiy ham qaytaradi (susaytirdi)
3) INVARIANT (o'zgarmas holat) va HISTORY (tarix) saqlanishi kerak
bola ota holat qoidalarini buzmasligi kerak
ota: immutable; bola: mutable qildi (invariant buzildi)
Bonus (tip nazariyasi):
- argument tiplari KONTRAVARIANT (kengroq bo'lishi mumkin)
- qaytish tiplari KOVARIANT (torroq bo'lishi mumkin)
- bola YANGI istisno (exception) TASHLAMASLIGI kerak// LSP buzilishi — bola PRECONDITION kuchaytiradi
class HisobRaqam {
yechib(summa: number): void {
// har qanday musbat summani yechishga ruxsat beradi
}
}
class OmonatHisob extends HisobRaqam {
yechib(summa: number): void {
if (summa > 1000) {
throw new Error("Omonat hisobdan 1000 dan ko'p yechib bo'lmaydi"); // qattiqroq talab!
}
}
}
// Kod `HisobRaqam` bilan ishlashga mo'ljallangan OmonatHisob berilsa,
// kutilmagan xato: mijoz kodi 5000 yechmoqchi edi, ota ruxsat berardi, bola bermaydi.
// To'g'ri — shartnoma buzilmaydi: cheklovni tur/model darajasida ifodala
interface Yechiladigan {
yechibBerish(summa: number): { muvaffaqiyat: boolean; xabar?: string };
}
class OmonatHisob2 implements Yechiladigan {
private limit = 1000;
yechibBerish(summa: number) {
if (summa > this.limit) {
return { muvaffaqiyat: false, xabar: "Limitdan oshdi" }; // istisno emas — kutilgan natija
}
return { muvaffaqiyat: true };
}
}LSP shartnoma: Liskov'ning asl mezoni — bola precondition'ni kuchaytirmasin (kamroq qabul qilmasin), postcondition'ni susaytirmasin (kamroq va'da qilmasin), invariant'ni saqlasin (ota holat qoidasini buzmasin) va yangi istisno tashlamasin. Klassik
HisobRaqammisolida bola qo'shimcha limit qo'ysa — mijoz kodi ota shartnomasiga ishonib xato yeydi. Yechim: cheklovni istisno bilan emas, aniq natija (result object) bilan ifodalash yoki ierarxiyani qayta modellashtirish. Bu qoidalar polimorfizmni (2.10) ishonchli qiladi — kod bola tipini bilmasdan ota bilan ishonch bilan ishlaydi.
2.8. I — Interface Segregation Principle (ISP)
Mijoz ishlatmaydigan metodlarga bog'liq bo'lishga majbur qilinmasligi kerak.
// YOMON — katta umumiy interfeys (hamma majbur)
interface Ishchi {
ishla(): void;
ovqatlan(): void;
uxla(): void;
}
class Robot implements Ishchi {
ishla() {}
ovqatlan() { throw new Error("Robot ovqatlanmaydi"); } // majbur — kerak emas!
uxla() { throw new Error("Robot uxlamaydi"); }
}
// YAXSHI — kichik, maxsus interfeyslar
interface Ishlaydigan { ishla(): void; }
interface Biologik { ovqatlan(): void; uxla(): void; }
class Robot implements Ishlaydigan { ishla() {} } // faqat kerakli
class Inson implements Ishlaydigan, Biologik { // ikkalasi
ishla() {} ovqatlan() {} uxla() {}
}ISP — katta umumiy interfeys o'rniga kichik, maxsus interfeyslar. Yomon:
IshchiinterfeysiRobotniovqatlan()ga majbur qiladi (kerak emas — bo'sh/xato implementatsiya). Yaxshi: ajratilgan interfeys (har klass faqat kerakligini oladi). "Many client-specific interfaces are better than one general-purpose." NestJS: kichik service interfeyslari. SRP'ning interfeys darajasidagi ko'rinishi.
2.9. D — Dependency Inversion Principle (DIP)
Yuqori darajali modul past darajaliga emas, ikkalasi ham abstraksiyaga bog'liq bo'lishi kerak.
// YOMON — yuqori modul konkret klassga bog'liq
class MySQLDatabase {
saqla(data: any) {}
}
class UserService {
private db = new MySQLDatabase(); // konkret bog'liqlik (qattiq)
yarat(user: any) { this.db.saqla(user); } // MySQL'ga bog'langan!
}
// YAXSHI — abstraksiyaga bog'liq (DI — 8.2)
interface Database { // abstraksiya
saqla(data: any): Promise<void>;
}
class MySQLDatabase implements Database { async saqla(d: any) {} }
class MongoDatabase implements Database { async saqla(d: any) {} } // almashtirsa bo'ladi
class UserService {
constructor(private db: Database) {} // abstraksiyaga (DI — 8.2)
async yarat(user: any) { await this.db.saqla(user); } // qaysi DB — farqi yo'q
}DIP — konkret klassga emas, abstraksiyaga (interfeys) tayaning. Yomon:
UserServiceMySQLDatabaseni to'g'ridan yaratadi (qattiq bog'langan — MySQL'ni almashtirib bo'lmaydi, test qiyin — 8.2: 2.2). Yaxshi:Databaseinterfeysiga bog'liq (DI orqali — istalgan implementatsiya). Bu — NestJS DI'ning yuragi 8.2-bob! Foyda: almashtiriladigan, test (mock — 8.11), moslashuvchan.
2.10. DIP va NestJS DI (bog'liqlik)
DIP — tamoyil (abstraksiyaga tayanish)
DI (Dependency Injection) — uni amalga oshirish usuli 8.2-bob
IoC konteyner — DI'ni avtomatlashtiruvchi (NestJS — 8.2)
NestJS'da:
constructor(private service: SomeService) {} DI (DIP amalda)
{ provide: ABSTRACTION, useClass: Implementation } abstraksiya almashtirish 8.2-bob
Siz 8.2 da DIP'ni amalda ishlatgansiz (nomini bilmasdan)DIP vs DI: DIP — tamoyil (abstraksiyaga tayanish — nima); DI — usul (konstruktor orqali berish — qanday — 8.2); IoC — DI'ni avtomatlashtiruvchi (NestJS konteyner — 8.2). Siz 8.2'da DIP'ni amalda ishlatgansiz!
{ provide, useClass }— abstraksiyani implementatsiyaga bog'lash. DIP — NestJS arxitekturasining falsafiy asosi.
2.11. SOLID birgalikda (sinergiya)
SOLID — alohida emas, BIRGALIKDA ishlaydi:
- SRP klasslarni kichik qiladi ISP interfeyslarni kichik
- DIP abstraksiya OCP kengaytirish LSP almashtirish ishonchli
- Hammasi test oson (mock — 8.11), o'zgarish xavfsiz
Natija: "Clean Architecture" 9.3-bob, DDD 9.4-bob — SOLID ustiga quriladiSinergiya: SOLID tamoyillari bir-birini kuchaytiradi. SRP (kichik klass) ISP (kichik interfeys); DIP (abstraksiya) OCP (kengaytirish) LSP (ishonchli almashtirish). Birgalikda — test oson 8.11-bob, o'zgarish xavfsiz, kengaytiriladigan. Clean Architecture 9.3-bob, DDD 9.4-bob — SOLID poydevoriga quriladi. Bu — arxitektura asosi.
2.11a. SOLID va Design Patterns aloqasi
Design pattern'lar 9.2-bob — ko'p hollarda SOLID tamoyillarini amalga oshiruvchi tayyor tuzilmalar. SOLID — "nima uchun"; pattern — "qanday". Ular bir tanganing ikki tomoni.
PATTERN 9.2-bob QAYSI SOLID'NI AMALGA OSHIRADI
──────────────────────────────────────────────────────
Strategy OCP (yangi strategiya = yangi klass) + DIP
Factory Method OCP (yangi mahsulot = yangi factory) + DIP
Abstract Factory OCP + DIP (oila darajasida abstraksiya)
Decorator OCP (xulqni o'rashda kengaytirish) + SRP
Adapter DIP (mos kelmagan interfeyslarni ulash) + ISP
Observer OCP (yangi kuzatuvchi = yangi klass) + SRP
Template Method OCP (qadamlarni override) — LSP ehtiyot bilan
Facade ISP (murakkab tizimga sodda interfeys)
Repository DIP + SRP (ma'lumot kirishini abstraksiyalash)SOLID Patterns: design pattern'lar 9.2-bob ko'pincha SOLID'ni jismoniy ko'rinishga keltiradi. Strategy va Factory — OCP'ning eng aniq tatbig'i (yangi xulq = yangi klass, eski tegmaydi); Decorator — OCP + SRP (kichik javobgarliklarni o'rash); Adapter — DIP + ISP; Repository — DIP + SRP. Muhim tushuncha: siz pattern'ni yodlab emas, SOLID muammosini yechish yo'lida qo'llaganingizda to'g'ri ishlatasiz. Pattern — vosita, SOLID — maqsad. Keyingi bob 9.2-bob ayni shu naqshlarni chuqur ochadi.
2.12. SOLID'ni haddan oshirmaslik (balans)
SOLID — qo'llanma, dogma emas:
- Over-engineering (har narsaga interfeys — keraksiz murakkablik)
- YAGNI ("You Aren't Gonna Need It" — kerak bo'lmaganni qilmaslik)
- KISS ("Keep It Simple" — oddiylik afzal)
Qoida: SOLID'ni MUAMMO bo'lganda qo'llang (o'zgarish, takror, test qiyinligi)
kichik skript uchun SOLID shart emas; katta tizim uchun zarurBalans: SOLID — qo'llanma, dogma emas. Har narsaga interfeys/abstraksiya — over-engineering (keraksiz murakkablik). YAGNI (kerak bo'lmaganni qilmaslik), KISS (oddiy tuting). SOLID'ni muammo paydo bo'lganda qo'llang (kod o'zgarishi qiyin, takror, test murakkab). Kichik skript — SOLID shart emas; katta/jamoaviy tizim — zarur. Pragmatizm muhim.
2.12a. Kod hidi (code smell) — SOLID buzilishining belgilari
"Kod hidi" (code smell) — kod ishlayotgan bo'lsa-da, chuqurroq dizayn muammosiga ishora qiluvchi tashqi belgi. Bu hidlar ko'pincha aynan biror SOLID tamoyilining buzilganini bildiradi. Refactoring — kodning tashqi xulqini o'zgartirmasdan ichki tuzilishini yaxshilash — mana shu hidlarga javob.
HID QAYSI TAMOYIL BUZILGAN DAVO
─────────────────────────────────────────────────────────────────
God Object / katta klass SRP klassni vazifalarga ajratish
Uzun metod (100+ qator) SRP mayda metodlarga bo'lish
Katta switch/if-else zanjiri OCP polimorfizm / Strategy 9.2-bob
Takroriy switch (bir xil tur) OCP Factory + polimorfizm
Bo'sh / throw qiluvchi metod LSP/ISP interfeys bo'lish / ierarxiya
`instanceof` bilan shoxlanish LSP polimorfizm
Ko'p metodli "fat" interfeys ISP mayda interfeyslarga bo'lish
`new Concrete()` service ichida DIP konstruktor injection
Import'lar juda ko'p (coupling) SRP/DIP mas'uliyat va bog'liqlikni kamaytirish
"Shotgun surgery" (bir SRP mas'uliyatni bir joyga jamlash
o'zgarish ko'p faylni tahrir)
"Feature envy" (klass boshqa SRP mantiqni tegishli klassga ko'chirish
klass ma'lumotini qazlaydi)Code smell: har bir hid — biror SOLID buzilishining sirtqi alomati. God Object va uzun metod — SRP; katta switch va takroriy shoxlanish — OCP; bo'sh/throw metod va
instanceof— LSP/ISP; fat interface — ISP;new Concrete()— DIP. Bu hidlarni code review'da (Misol 8) tanish — refactoring uchun signal. Muhim: hid ≠ xato; kod ishlaydi, lekin kelajakdagi o'zgarishlarni qiyinlashtiradi.
2.12b. Refactoring qadamlari — xavfsiz o'zgartirish tartibi
SOLID'ga muvofiq refactoring — bir zumda emas, kichik, xavfsiz qadamlar bilan. Har qadamdan keyin testlar yashil bo'lishi shart 8.11-bob.
1) TESTLAR bilan qopla (yoki mavjudligini tekshir)
refactoring xulqni o'zgartirmasligini kafolatlaydigan to'siq
2) HIDNI aniqla (2.12a jadvali) va qaysi tamoyil buzilganini belgila
3) KICHIK qadam tanla:
- Extract Method / Extract Class (SRP)
- Introduce Interface + Replace Conditional with Polymorphism (OCP)
- Replace Inheritance with Delegation/Composition (LSP)
- Split Interface (ISP)
- Dependency Injection kirit (DIP)
4) BITTA qadamni bajar testlarni ishga tushir yashil bo'lsa commit
5) Takrorla (2-qadamga qayt) — hid yo'qolguncha
6) TO'XTA — muvozanat: yetarli darajaga yetganda to'xta (YAGNI — 2.12)Refactoring qadamlari: xavfsiz refactoring — testga tayangan iteratsiya. Avval testlar bilan qoplang (xulq kafolati), hidni aniqlang, bitta kichik qadam bajaring (Extract Class, Introduce Interface, Replace Conditional with Polymorphism, Split Interface, Inject Dependency), testni yashil ko'ring, commit qiling — hid yo'qolguncha takrorlang. Katta "big-bang" refactoring xavfli (regressiya); mayda qadamlar — har doim ishlaydigan holatda qoladi. To'xtash ham san'at: over-engineering'gacha bormang 2.12-bob.
2.12c. SOLID va TypeScript — til vositalari
TypeScript SOLID'ni ifodalash uchun boy vositalar beradi, lekin ba'zi nozikliklarga ega:
// interface — sof shartnoma (runtime'da yo'q — 7-qism)
interface Repository<T> { topById(id: number): Promise<T>; }
// abstract class — qisman implementatsiya + shartnoma (runtime'da bor)
abstract class BaseService {
abstract bajar(): void; // bola majburan implement qiladi
protected log(m: string) {} // umumiy xulq (meros)
}
// generics — LSP/ISP'ni tip darajasida ta'minlaydi (universal, tur-xavfsiz)
interface Oqiladigan<T> { topAll(): Promise<T[]>; }
// `implements` — bir klass BIR NECHA mayda interfeysni qo'shishi mumkin (ISP)
class ProductRepo implements Oqiladigan<Product>, Yoziladigan<Product> {}SOLID va TypeScript:
interface— sof shartnoma, lekin runtime'da yo'qoladi (7-qism) — shu bois DIP uchun NestJS token (Symbol) kerak (8.2, 3-xato).abstract class— shartnoma va umumiy implementatsiya (runtime'da mavjud,instanceofishlaydi) — umumiy xulq bo'lsa afzal. Generiklar (<T>) LSP va ISP'ni tur-xavfsiz, universal qiladi (bir interfeys ko'p tipga).implementsbir klassga bir necha mayda interfeys biriktirishga imkon beradi — bu aynan ISP'ni amalga oshiradi. To'g'ri vosita tanlash — SOLID'ni TypeScript'da tabiiy ifodalaydi.
2.13. Best practices (SOLID)
SRP — bir klass bir vazifa (controller/service/repo — 8.1)
OCP — o'zgaruvchan joyda interfeys (to'lov, bildirishnoma — 2.4)
LSP — meros faqat haqiqiy is-a (shubhada kompozitsiya — 2.7)
ISP — kichik maxsus interfeys (katta umumiy emas — 2.8)
DIP — abstraksiyaga tayaning (DI — NestJS — 2.9, 8.2)
Sinergiya (birgalikda — test/o'zgarish — 2.11)
Balans (YAGNI/KISS — over-engineering emas — 2.12)3. Sintaksis — tez ma'lumotnoma
// SRP 2.2-bob: bir klass bir vazifa
class UserRepository {} class EmailService {} class UserValidator {}
// OCP 2.4-bob: interfeys + yangi klass
interface TolovUsuli { tolov(s: number): Promise<void>; }
class UzumTolov implements TolovUsuli {} // eski o'zgarmaydi
// LSP 2.6-bob: bola ota o'rniga ishlaydi (shartnoma buzilmaydi)
// ISP 2.8-bob: kichik interfeyslar
interface Ishlaydigan { ishla(): void; }
// DIP 2.9-bob: abstraksiyaga tayaning (DI)
constructor(private db: Database) {} // interfeys, konkret emas4. Batafsil kod namunalari
Misol 1 — SRP: buyurtma tizimi (2.2)
// YOMON — OrderService hamma narsa qiladi
class OrderService {
yarat(data: any) {
// validatsiya + narx hisoblash + DB + email + log — 5 vazifa!
}
}
// YAXSHI — har vazifa alohida (NestJS — 8.1)
class OrderValidator { // 1. validatsiya (8.5)
tekshir(data: CreateOrderDto): void {}
}
class PriceCalculator { // 2. narx
hisobla(items: Item[]): number { return 0; }
}
class OrderRepository { // 3. DB (8.3)
async saqla(order: Order): Promise<Order> { return order; }
}
class OrderNotifier { // 4. xabar (8.10)
async xabarBer(order: Order): Promise<void> {}
}
class OrderService { // orkestratsiya (yupqa)
constructor(
private validator: OrderValidator,
private calculator: PriceCalculator,
private repository: OrderRepository,
private notifier: OrderNotifier,
) {}
async yarat(data: CreateOrderDto): Promise<Order> {
this.validator.tekshir(data);
const summa = this.calculator.hisobla(data.items);
const order = await this.repository.saqla({ ...data, summa } as Order);
await this.notifier.xabarBer(order);
return order;
}
}Misol 2 — OCP: bildirishnoma kanallari (2.4)
// Yangi kanal qo'shish — mavjud kod o'zgarmaydi (OCP)
interface XabarKanali {
yubor(qabuluvchi: string, matn: string): Promise<void>;
}
class EmailKanal implements XabarKanali { // (8.10)
async yubor(email: string, matn: string) { /* SMTP */ }
}
class SmsKanal implements XabarKanali { // (5.18)
async yubor(tel: string, matn: string) { /* Eskiz */ }
}
class TelegramKanal implements XabarKanali { // (8.12)
async yubor(chatId: string, matn: string) { /* bot */ }
}
class PushKanal implements XabarKanali { // YANGI — eski tegmaydi!
async yubor(token: string, matn: string) { /* FCM */ }
}
class XabarService {
constructor(private kanallar: XabarKanali[]) {} // ko'p kanal (DIP — 2.9)
async hammaga(qabuluvchi: string, matn: string) {
await Promise.all(this.kanallar.map((k) => k.yubor(qabuluvchi, matn)));
}
}
// Yangi kanal: PushKanal yoz + ro'yxatga qo'sh XabarService o'zgarmaydiMisol 3 — LSP: to'g'ri ierarxiya (2.6)
// YOMON — Kvadrat Kvadratdan meros (LSP buzadi)
class Tortburchak {
constructor(protected en: number, protected boy: number) {}
enQoy(en: number) { this.en = en; }
boyQoy(boy: number) { this.boy = boy; }
yuza() { return this.en * this.boy; }
}
class Kvadrat extends Tortburchak {
enQoy(en: number) { this.en = en; this.boy = en; } // boy ham o'zgaradi — kutilmagan!
}
// uzunlik=5, balandlik=4 kutilsa Kvadrat 4×4 (LSP buzildi)
// YAXSHI — umumiy abstraksiya (meros emas)
interface Shakl {
yuza(): number;
}
class Tortburchak2 implements Shakl {
constructor(private en: number, private boy: number) {}
yuza() { return this.en * this.boy; }
}
class Kvadrat2 implements Shakl {
constructor(private tomon: number) {}
yuza() { return this.tomon ** 2; }
}Misol 4 — ISP: repository interfeyslar (2.8)
// YOMON — katta repository interfeysi
interface Repository<T> {
topAll(): Promise<T[]>;
topById(id: number): Promise<T>;
yarat(data: T): Promise<T>;
yangila(id: number, data: T): Promise<T>;
ochir(id: number): Promise<void>;
qidirText(matn: string): Promise<T[]>; // hamma model'ga kerak emas
aggregate(): Promise<any>; // faqat ba'zilarga
}
// YAXSHI — kichik, kerakli interfeyslar
interface Oqiladigan<T> {
topAll(): Promise<T[]>;
topById(id: number): Promise<T>;
}
interface Yoziladigan<T> {
yarat(data: T): Promise<T>;
yangila(id: number, data: T): Promise<T>;
ochir(id: number): Promise<void>;
}
interface Qidiriladigan<T> {
qidir(matn: string): Promise<T[]>;
}
// Faqat o'qiladigan repo (read-only — masalan, hisobot)
class ReportRepository implements Oqiladigan<Report> {
async topAll() { return []; }
async topById(id: number) { return {} as Report; }
}
// To'liq CRUD + qidiruv
class ProductRepository implements Oqiladigan<Product>, Yoziladigan<Product>, Qidiriladigan<Product> {
/* hamma metod */
}Misol 5 — DIP: to'liq (interfeys + DI — 2.9)
// Abstraksiyalar (yuqori daraja shular ga tayanadi)
interface UserRepository {
topByEmail(email: string): Promise<User | null>;
saqla(user: User): Promise<User>;
}
interface PasswordHasher {
hash(parol: string): Promise<string>;
tekshir(parol: string, hash: string): Promise<boolean>;
}
interface TokenService {
yarat(payload: object): string;
}
// Yuqori daraja — FAQAT abstraksiyalarga tayanadi (DIP)
class AuthService {
constructor(
private users: UserRepository, // interfeys
private hasher: PasswordHasher, // interfeys
private tokens: TokenService, // interfeys
) {}
async login(email: string, parol: string): Promise<string> {
const user = await this.users.topByEmail(email);
if (!user || !(await this.hasher.tekshir(parol, user.parolHash))) {
throw new Error("Email yoki parol noto'g'ri"); // (8.9)
}
return this.tokens.yarat({ sub: user.id });
}
}
// Implementatsiyalar (past daraja — abstraksiyani amalga oshiradi)
class TypeOrmUserRepository implements UserRepository { /* TypeORM — 8.3 */ }
class BcryptHasher implements PasswordHasher { /* bcrypt — 5.15 */ }
class JwtTokenService implements TokenService { /* JWT — 8.9 */ }
// AuthService TypeORM/bcrypt/JWT'ni BILMAYDI (almashtirsa bo'ladi, test oson — mock)Misol 6 — SOLID NestJS'da (real — 2.10)
// NestJS — SOLID amalda
// DIP — interfeys token (8.2)
export const USER_REPOSITORY = Symbol("USER_REPOSITORY");
interface IUserRepository { // abstraksiya (DIP, ISP)
topById(id: number): Promise<User>;
}
@Injectable()
class UsersService { // SRP — faqat user biznes mantiq
constructor(
@Inject(USER_REPOSITORY) private repo: IUserRepository, // DIP (8.2)
) {}
bitta(id: number) { return this.repo.topById(id); }
}
// Module — implementatsiyani bog'lash (OCP — almashtirsa bo'ladi)
@Module({
providers: [
UsersService,
{ provide: USER_REPOSITORY, useClass: TypeOrmUserRepository }, // yoki MongoUserRepository
],
})
export class UsersModule {}
// DB almashtirsa: faqat useClass o'zgaradi (UsersService tegmaydi — DIP+OCP)Misol 7 — Strategy bilan OCP (9.2 ko'prik)
// Chegirma strategiyalari (OCP — yangi chegirma yangi klass)
interface ChegirmaStrategiyasi {
hisobla(summa: number): number;
}
class ChegirmaYoq implements ChegirmaStrategiyasi {
hisobla(s: number) { return s; }
}
class FoizChegirma implements ChegirmaStrategiyasi {
constructor(private foiz: number) {}
hisobla(s: number) { return s * (1 - this.foiz / 100); }
}
class VipChegirma implements ChegirmaStrategiyasi { // YANGI
hisobla(s: number) { return s > 1000000 ? s * 0.8 : s * 0.9; }
}
class Savatcha {
constructor(private chegirma: ChegirmaStrategiyasi) {} // strategiya (DIP)
yakuniyNarx(summa: number) { return this.chegirma.hisobla(summa); }
}
// new Savatcha(new VipChegirma()) — yangi strategiya, Savatcha o'zgarmaydi (OCP)Misol 8 — SOLID buzilishini aniqlash (refactoring)
// SOLID buzilish belgilari va tuzatish:
// SRP buzilishi: "UserManager" (Manager = ko'p vazifa — 2.3)
// User, UserRepository, UserValidator, UserNotifier
// OCP buzilishi: katta switch/if (yangi tur o'zgartirish — 2.4)
// Strategy/Factory pattern (interfeys)
// LSP buzilishi: override metod throw qiladi (2.6)
// ierarxiyani qayta ko'rib chiq / kompozitsiya
// ISP buzilishi: implements'da bo'sh/throw metodlar (2.8)
// interfeysni bo'lish
// DIP buzilishi: "new ConcreteClass()" service ichida (2.9)
// constructor injection (DI)Misol 9 — Kompozitsiya vs meros (LSP — 2.7)
// Meros muammosi (LSP xavfi)
class Massiv extends Array {} // murakkab, kutilmagan xulq
// Kompozitsiya (has-a — "composition over inheritance")
class Stack<T> {
private items: T[] = []; // has-a (massivga ega)
push(item: T) { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
get size() { return this.items.length; }
}
// faqat kerakli metodlar (ISP), kutilmagan meros yo'q (LSP), aniq (SRP)Misol 10 — To'liq SOLID arxitektura (sinergiya — 2.11)
// E-commerce buyurtma — barcha SOLID birga
interface IOrderRepository { saqla(o: Order): Promise<Order>; } // DIP, ISP
interface IPaymentGateway { tolov(summa: number): Promise<boolean>; } // DIP, OCP
interface IInventory { kamaytir(items: Item[]): Promise<void>; } // DIP, ISP
interface INotifier { yubor(order: Order): Promise<void>; } // DIP, ISP
// SRP — faqat buyurtma orkestratsiyasi
class CreateOrderUseCase {
constructor(
private orders: IOrderRepository,
private payment: IPaymentGateway, // Click/Payme/Uzum almashtirsa bo'ladi (OCP)
private inventory: IInventory,
private notifier: INotifier,
) {}
async bajar(dto: CreateOrderDto): Promise<Order> {
const tolovOk = await this.payment.tolov(dto.summa); // qaysi to'lov — farqi yo'q (DIP)
if (!tolovOk) throw new Error("To'lov amalga oshmadi");
await this.inventory.kamaytir(dto.items);
const order = await this.orders.saqla(dto as any);
await this.notifier.yubor(order); // qaysi kanal — farqi yo'q (OCP)
return order;
}
}
// Test: barcha bog'liqlik mock 8.11-bob; o'zgarish lokal; kengaytirish xavfsiz
// Bu — Clean Architecture use-case 9.3-bob ga ko'prik5. To'g'ri va noto'g'ri holatlar
1) "Manager"/"Util" katta klass
UserManager (login + email + DB + log — SRP buzilishi)
ajratilgan klasslar (har biri bir vazifa — 2.2)2) Yangi tur switch/if qo'shish
if (tur === "yangi") (OCP buzilishi — 2.4)
interfeys + yangi klass (Strategy)3) new ConcreteClass() service ichida
this.db = new MySQLDatabase() (DIP buzilishi — 2.9)
constructor(private db: Database) (DI)4) Override metod throw qiladi
ucha() { throw } (LSP buzilishi — 2.6)
to'g'ri ierarxiya / kompozitsiya5) Har narsaga interfeys (over-engineering)
oddiy skriptga 10 ta interfeys (YAGNI buzilishi — 2.12)
muammo bo'lganda SOLID (balans)6. Keng tarqalgan xatolar va yechimlari
Xato 1 — SOLID'ni dogma sifatida
Sababi: har joyga qo'llash (over-engineering — 2.12). Yechimi: muammo bo'lganda; YAGNI/KISS.
Xato 2 — SRP'ni juda mayda
Sababi: har metod alohida klass (haddan oshirish). Yechimi: "bir o'zgarish sababi" — mantiqiy guruh.
Xato 3 — Interfeys'siz DIP "qila olmaslik"
Sababi: TS interfeys runtime'da yo'q (7). Yechimi: NestJS token (Symbol — 8.2); abstrakt klass.
Xato 4 — LSP'ni e'tiborsiz (meros suiiste'mol)
Sababi: "is-a" emas joyda extends. Yechimi: kompozitsiya (has-a — 2.7).
Xato 5 — OCP haddan oshirish
Sababi: o'zgarmaydigan joyga abstraksiya. Yechimi: o'zgaruvchan joyga (to'lov, kanal).
Xato 6 — SOLID bilsa-yu, qo'llamaslik
Sababi: nazariya amaliyot uzilishi. Yechimi: code review'da SOLID tekshir; refactoring (Misol 8).
7. Integratsiya — bu mavzu stack'ning qayerida uchraydi
- OOP (2.10, 7): SOLID — OOP dizayni.
- NestJS DI 8.2-bob: DIP amalda.
- Modul 8.1-bob: SRP (controller/service/repo).
- Design Patterns 9.2-bob: SOLID'ni amalga oshiradi.
- Clean Architecture 9.3-bob: SOLID ustiga.
- DDD 9.4-bob: SOLID bilan.
- Test 8.11-bob: DIP — mock.
- Clean code (15): SOLID — sifat.
8. Eng yaxshi amaliyotlar (best practices)
- SRP — bir klass bir vazifa (controller/service/repo — 8.1, 2.2).
- OCP — o'zgaruvchan joyda interfeys (to'lov, kanal — 2.4).
- LSP — meros faqat haqiqiy is-a (shubhada kompozitsiya — 2.7).
- ISP — kichik maxsus interfeys 2.8-bob.
- DIP — abstraksiyaga tayaning (DI — NestJS — 2.9).
- Sinergiya (birgalikda — test/o'zgarish — 2.11).
- Balans (YAGNI/KISS — over-engineering emas — 2.12).
- Code review'da SOLID (buzilish belgilari — Misol 8).
- Kompozitsiya > meros (LSP xavfi — 2.7).
- Token/abstrakt klass (TS interfeys runtime'da yo'q — 8.2).
9. Amaliy loyiha: "SOLID Refactoring"
SOLID'ni amalda mustahkamlash.
Maqsad
Mavjud "yomon" (SOLID buzilgan) kodni SOLID tamoyillari bo'yicha refactoring qilish.
Talablar (requirements)
- SRP: katta klassni ajratish (validator/repo/notifier — Misol 1, 2.2).
- OCP: switch/if Strategy (to'lov/chegirma — Misol 2, 7, 2.4).
- LSP: noto'g'ri merosni tuzatish (kompozitsiya — Misol 3, 9, 2.6).
- ISP: katta interfeysni bo'lish (Misol 4, 2.8).
- DIP: new ConcreteClass DI (Misol 5, 2.9).
- NestJS: token + useClass (Misol 6, 2.10).
- Sinergiya: to'liq use-case (Misol 10, 2.11).
- Test: DIP tufayli mock 8.11-bob.
- Balans: over-engineering'dan qochish (YAGNI — 2.12).
- Refactoring: buzilish belgilarini topish (Misol 8).
Maslahatlar (hint)
- SRP: "bir o'zgarish sababi" 2.3-bob.
- OCP: o'zgaruvchan joy 2.5-bob.
- DIP: interfeys token (TS — 8.2, 3-xato).
- LSP: kompozitsiya 2.7-bob.
- Balans: muammo bo'lganda 2.12-bob.
"Tayyor" mezonlari (acceptance criteria)
- SRP (ajratilgan klasslar).
- OCP (Strategy).
- LSP (to'g'ri ierarxiya).
- ISP (kichik interfeys).
- DIP (DI).
- NestJS (token + useClass).
- To'liq use-case.
- Test (mock).
- Balans (over-engineering yo'q).
- Refactoring (belgilar).
Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.
10. Xulosa va keyingi bobga ko'prik
Bu bobda arxitekturaning poydevori — SOLID'ni chuqur o'rgandik:
- SRP (bir klass bir vazifa — 2.2, 2.3); OCP (kengaytirishga ochiq, o'zgartirishga yopiq — 2.4, 2.5).
- LSP (bola ota o'rniga ishlaydi — 2.6, 2.7); ISP (kichik maxsus interfeys — 2.8).
- DIP (abstraksiyaga tayanish — DI — 2.9, 2.10 — NestJS yuragi).
- Sinergiya (birgalikda — 2.11); balans (YAGNI/KISS — 2.12).
Keyingi bob — 9.2-bob: Design Patterns (dizayn naqshlari). SOLID tamoyillarini bildik; endi ularni amalga oshiradigan tayyor yechimlarni — design pattern'lar — o'rganamiz: creational (Singleton, Factory), structural (Adapter, Decorator), behavioral (Strategy, Observer). Bu — keng tarqalgan muammolarga sinovdan o'tgan yechimlar (umumiy til — har dasturchi biladi).
Foydalanilgan rasmiy/ishonchli manbalar
- Robert C. Martin — "Clean Architecture: A Craftsman's Guide to Software Structure and Design" (SOLID tamoyillarining asosiy manbasi)
- Robert C. Martin — "Agile Software Development, Principles, Patterns, and Practices" (SOLID atamasi birinchi tizimli bayon qilingan asar)
- Robert C. Martin — "Clean Code: A Handbook of Agile Software Craftsmanship" (SRP va kod sifati)
- Barbara Liskov, Jeannette Wing — "A Behavioral Notion of Subtyping" (LSP ning ilmiy asosi)
- Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides ("Gang of Four") — "Design Patterns: Elements of Reusable Object-Oriented Software" (SOLID'ni amalga oshiruvchi naqshlar)
- TypeScript rasmiy hujjatlari — interfeys, abstrakt klass, generiklar (typescriptlang.org)
- NestJS rasmiy hujjatlari — Dependency Injection va Providers (DIP amaliyoti — docs.nestjs.com)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!