WisarWisar
Dasturlash kitobi/9-QISM — Arxitektura27 daqiqa

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

text
  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'zgaradigan

SOLID — 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.

typescript
//  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 User ham 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)

text
  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.

typescript
//  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 if qo'shish (mavjud kod o'zgaradi xato xavfi, qayta test). Yaxshi: interfeys + yangi klass (UzumTolov qo'shiladi, eski tegmaydi). Strategy pattern 9.2-bob — OCP amalda. Foyda: yangi xususiyat xavfsiz (eski kod sinmaydi).

2.5. OCP — abstraksiya kuchi

text
  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.

typescript
//  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: Pingvin Qush'dan meros, lekin ucha() 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

text
  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/instanceof kerak. 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:

text
  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
typescript
//  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 HisobRaqam misolida 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.

typescript
//  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: Ishchi interfeysi Robotni ovqatlan()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.

typescript
//  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: UserService MySQLDatabaseni to'g'ridan yaratadi (qattiq bog'langan — MySQL'ni almashtirib bo'lmaydi, test qiyin — 8.2: 2.2). Yaxshi: Database interfeysiga 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)

text
  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)

text
  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 quriladi

Sinergiya: 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.

text
  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)

text
   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 zarur

Balans: 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.

text
  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.

text
  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:

typescript
// 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, instanceof ishlaydi) — umumiy xulq bo'lsa afzal. Generiklar (<T>) LSP va ISP'ni tur-xavfsiz, universal qiladi (bir interfeys ko'p tipga). implements bir 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)

text
   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

typescript
// 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 emas

4. Batafsil kod namunalari

Misol 1 — SRP: buyurtma tizimi (2.2)

typescript
//  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)

typescript
// 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'zgarmaydi

Misol 3 — LSP: to'g'ri ierarxiya (2.6)

typescript
//  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)

typescript
//  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)

typescript
// 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)

typescript
// 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)

typescript
// 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)

typescript
// 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)

typescript
//  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)

typescript
// 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'prik

5. To'g'ri va noto'g'ri holatlar

1) "Manager"/"Util" katta klass

text
 UserManager (login + email + DB + log — SRP buzilishi)
 ajratilgan klasslar (har biri bir vazifa — 2.2)

2) Yangi tur switch/if qo'shish

text
 if (tur === "yangi") (OCP buzilishi — 2.4)
 interfeys + yangi klass (Strategy)

3) new ConcreteClass() service ichida

text
 this.db = new MySQLDatabase() (DIP buzilishi — 2.9)
 constructor(private db: Database) (DI)

4) Override metod throw qiladi

text
 ucha() { throw } (LSP buzilishi — 2.6)
 to'g'ri ierarxiya / kompozitsiya

5) Har narsaga interfeys (over-engineering)

text
 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)

  1. SRP: katta klassni ajratish (validator/repo/notifier — Misol 1, 2.2).
  2. OCP: switch/if Strategy (to'lov/chegirma — Misol 2, 7, 2.4).
  3. LSP: noto'g'ri merosni tuzatish (kompozitsiya — Misol 3, 9, 2.6).
  4. ISP: katta interfeysni bo'lish (Misol 4, 2.8).
  5. DIP: new ConcreteClass DI (Misol 5, 2.9).
  6. NestJS: token + useClass (Misol 6, 2.10).
  7. Sinergiya: to'liq use-case (Misol 10, 2.11).
  8. Test: DIP tufayli mock 8.11-bob.
  9. Balans: over-engineering'dan qochish (YAGNI — 2.12).
  10. 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!
9.1-bob: SOLID prinsiplari — Wisar