SOLID prinsiplari: TypeScript misollari bilan

Assalamu Alaykum bugun SOLID haqida gaplashamiz .SOLID bu 5 ta asosiy printsiplardan iborat bo'lgan obyektga yo'naltirilgan software design bo'lib kodning qayta ishlatish , tez kengaytirish va uni keyinchalik ham bir xil ishlashligiga qaratilgan . Bu printsiplar 2000 yillarda Robert C. Martin yoki Uncle Bob nomi bilan tanilgan odam tomonidan kiritilgan va o'shandan beri software engineeringni fundamental konseptsiyalari bo'lib kelmoqda .

SOLID prinstsiplari :

  1. Single Responsibility Principle (SRP)

  2. Open/Closed Principle (OCP)

  3. Liskov Substitution Principle (LSP)

  4. Interface Segregation Principle (ISP)

  5. Dependency Inversion Principle (DIP)

Bularni o'zbekchaga tarjima qilolmadi balkim yoqdir ham ;).

Qani endi har bir printsipga to'xtalib va ularning praktikada qo'llanilishini ko'rib chiqamiz .

Single Responsibility Principle (SRP)

Single Responsibility Principle (SRP) - bu class faqatgina bir narsaga javob berishi zarur . Bu printsip kichkana va asosan bir narsaga yo'naltirilgan classlarni yaratishga undaydi ,chunki keyinchalik ularni o'zgartirish va xatolarni to'g'rilash osonroq bo'ladi.

Masalan, Auth va fayl I/O larni boshqaradigan class bor deb hisoblang . Bu SRP printsipini buzadi , chunki unda 2 ta masulyat bor : authentication va fayl I/O. Buning o'rniga , biz bu classni 2 ga bo'lishimiz kerak : bittasi authentication va boshqasi fayl I/O uchun.

interface DBConnection {
  // Databasega ulanish
}

class UserAuthenticator {
  constructor(private dbConnection: DBConnection) {}

  public authenticateUser(username: string, password: string): boolean {
    // User authenticate logikasi
    return true;
  }
}

class FileIO {
  public readFile(filename: string): string {
    // Fayl o`iqb olish logikasi
    return '';
  }

  public writeFile(filename: string, data: string): void {
    // Faylga yozish logikasi
  }
}

Tepadagi kodda UserAuthenticator user authentication logikani boshqaradi va FileIO esa fayl I/O logikani boshqaradi.

Open/Closed Principle (OCP)

Open/Closed Principle (OCP) - software entities (classlar, modullar, funksiyalar , va boshqalar .) kengayish uchun ochiq lekin o'zgarish uchun yopiq bo'lishi zarur . Bu biz oldingi kodni o'zgartirmagan holda yangi qo'shimchalarni qo'sha olishimiz zarurligimizni anglatadi .

Masalan , Bizda hisobotlarni har xil formatlarda (PDF, HTML, va CSV) chiqarib beradigan class bor. Qachonki biz yangi qo'shimcha qo'shganda har doim classni o'zgartirish o'rniga , ReportGenerator interfeysini implement qiladigan yangi class yaratib va yangi funksionallikni qo'shishimiz mumkin.

interface ReportGenerator {
  generateReport(): string;
}

class PDFReportGenerator implements ReportGenerator {
  generateReport(): string {
    // Generate PDF report logic
    return '';
  }
}

class HTMLReportGenerator implements ReportGenerator {
  generateReport(): string {
    // Generate HTML report logic
    return '';
  }
}

class CSVReportGenerator implements ReportGenerator {
  generateReport(): string {
    // Generate CSV report logic
    return '';
  }
}

Ushbu misolda bizda turli formatlarda hisobotlarni yaratish uchun sinf iyerarxiyasi mavjud. ReportGenerator interfeysiga amal qiladigan va yangi funksiyalarni qo’shadigan yangi sinf yaratish orqali yangi hisobot formatlarini osongina qo’shishimiz mumkin.

Liskov Substitution Principle (LSP)

Liskov Substitution Principle (LSP) —Superclassning obyektlari o'zining bola classlari bilan dasturning to'g'ri ishlashiga tasir qilmagan holda almashtirish mumkin bo'lishi zarur .Qisqa qilib aytganda Ota class obyekti bolas class obyekti bilan almashtirilganda dastur xatoliklarsiz ishlashi zarur .

Masalan, Rectangle (to'rtburchak) classi bor — uning width va height xususiyatlarini alohida o'zgartirsa bo'ladi. Square (kvadrat) esa "to'rtburchakning bir turi" deb Rectangledan meros oladi. Ammo kvadratda eni va bo'yi doim teng, shuning uchun Square bittasini o'zgartirganda ikkinchisini ham o'zgartirishga majbur bo'ladi — bu ota classning shartnomasini buzadi:

class Rectangle {
  constructor(protected width: number, protected height: number) {}
  setWidth(w: number) { this.width = w; }
  setHeight(h: number) { this.height = h; }
  area(): number { return this.width * this.height; }
}

class Square extends Rectangle {
  setWidth(w: number)  { this.width = w; this.height = w; }  // yon ta'sir!
  setHeight(h: number) { this.width = h; this.height = h; }
}

// Rectangle uchun yozilgan kod Square kelganda buziladi:
function resize(rect: Rectangle) {
  rect.setWidth(5);
  rect.setHeight(4);
  return rect.area(); // Rectangle uchun 20 kutiladi...
}

resize(new Rectangle(0, 0)); // 20 ✅
resize(new Square(0));       // 16 ❌ — LSP buzildi

Ushbu misolda resize funksiyasi Rectangle bilan ishlashga mo'ljallangan va natija 20 bo'lishini kutadi. Lekin unga Square berilsa 16 qaytadi, chunki setWidth eni bilan birga bo'yni ham o'zgartirib yuboradi. TypeScript buni xatosiz qabul qiladi (Square — Rectanglening bola classi), lekin dastur noto'g'ri ishlaydi — mana shu LSP buzilishi. To'g'ri yechim: Square ni Rectangledan meros oldirmaslik, ularni alohida shakllar sifatida modellashtirish kerak.

Interface Segregation Principle (ISP)

The Interface Segregation Principle (ISP) — klient ishlatmaydigan interfeysga bog'liq bo'lishga majburlanmasligi zarur. Boshqacha qilib aytganda biz katta va umumiy interfeylar o'rniga kichik va maxsus interfeyslar yaratishimiz zarur.

Masalan , bizda Printer nomli interfeys mavjud va unda 2 metod bor: print(chiqar)va scan(skanerla). Agarda klient faqat dokument chiqarsa u scan metodi implementatsiya qilishga majburlanmasligi zarur. O'rniga ,biz alohida 2 ta ajratilgan interfeys yarata olamiz va kerak bo'lganda ikkalasini birlashtirishimiz mumkin.

interface Printer {
  print(): void;
}

interface Scanner {
  scan(): void;
}

class AllInOnePrinter implements Printer, Scanner {
  print(): void {
    // Print logic
  }

  scan(): void {
    // Scan logic
  }
}

class SimplePrinter implements Printer {
  print(): void {
    // Print logic
  }
}

Ushbu misolda bizda print va skanerlash uchun alohida interfeyslar mavjud. Shuningdek, bizda ushbu interfeyslarni amalga oshiradigan ikkita sinf mavjud: AllInOnePrinter ikkala printerni ham, skanerni ham, SimplePrinter esa faqat Printerni amalga oshiradi.

Dependency Inversion Principle (DIP)

Dependency Inversion Principle (DIP) — yuqori darajadagi modullar past darajadagi modullarga bog'liq bolmasligi zarur.Buning o'rniga , ikkalasi ham abstraktsiyalarga bog’liq bo’lishi kerak.Abstraktsiyalar tafsilotlarga bog’liq bo’lmasligi kerak. Tafsilotlar abstraktsiyalarga bog’liq bo’lishi kerak.

Misol uchun, bizda ma’lumotlar bazasidan ma’lumotlarni o’qiydigan sinf bor deylik. Ma’lumotlar bazasiga to’g’ridan-to’g’ri kirish o’rniga, biz yuqori darajadagi modulni ma’lumotlarga kirishning past darajadagi tafsilotlaridan ajratib turadigan abstraksiya qatlamini yaratishimiz mumkin. Shunday qilib, yuqori darajadagi modul ma’lumotlarga kirish tafsilotlariga emas, balki abstraktsiya qatlamiga bog’liq.

interface DBConnection {
  // DB connection methods
}

interface DataAccessLayer {
  getData(): string;
}

class DBDataAccessLayer implements DataAccessLayer {
  constructor(private dbConnection: DBConnection) {}

  getData(): string {
    // Get data from DB logic
    return '';
  }
}

class FileDataAccessLayer implements DataAccessLayer {
  constructor(private filename: string) {}

  getData(): string {
    // Get data from file logic
    return '';
  }
}

Ushbu misolda bizda yuqori darajadagi modulni ma’lumotlarga kirishning past darajadagi tafsilotlaridan ajratib turadigan ma’lumotlarga kirish uchun abstraktsiya qatlami mavjud. Bizda ushbu interfeysning ikkita alohida ilovasi mavjud: DBDataAccessLayer va FileDataAccessLayer. Yuqori darajadagi modul ma’lumotlarga kirish tafsilotlariga emas, balki abstraktsiya qatlamiga bog’liq.

Abstraktsiya qatlami nima?

Abstraktsiya qatlami — bu dasturning yuqori darajadagi mantiqini amalga oshirishning past darajadagi tafsilotlaridan ajratib turadigan dasturiy ta’minotni loyihalash konsepsiyasi. U amalga oshirish tafsilotlarini mavhumlashtiradigan interfeysni taqdim etadi, bu esa yuqori darajadagi mantiqqa asosiy dastur qanday ishlashini bilmasdan interfeys bilan ishlashga imkon beradi.

Boshqacha qilib aytganda, abstraktsiya qatlami murakkab tizimning soddalashtirilgan, mavhum ko’rinishini ta’minlaydi. Bu yuqori darajadagi mantiqqa tizimning kaput ostida qanday ishlashini bilmasdan, tizim bilan o’zaro ta’sir qilish imkonini beradi. Bu tizimni yanada moslashuvchan, texnik xizmat ko’rsatish va vaqt o’tishi bilan o’zgartirishni osonlashtiradi.

Misol uchun, men bog’liqlikni o’zgartirish printsipi (DIP) uchun taqdim etgan kod misolida DataAccessLayer interfeysi yuqori darajadagi mantiqni ma’lumotlarga kirishning past darajadagi tafsilotlaridan ajratib turadigan abstraksiya qatlamidir. DBDataAccessLayer va FileDataAccessLayer sinflari ushbu interfeysning turli xil ilovalarini ta’minlaydi, ammo DataAccessLayer interfeysiga bog’liq bo’lgan yuqori darajadagi modul qaysi dasturdan foydalanayotganini bilishi shart emas. U shunchaki interfeys bilan o’zaro aloqada bo’lishi va asosiy dastur ma’lumotlarga kirish tafsilotlarini hal qilishiga ishonishi mumkin. Bu yuqori darajadagi modulni yanada moslashuvchan va vaqt o’tishi bilan o’zgartirishni osonlashtiradi, chunki u ma’lumotlarga kirish tafsilotlari bilan mahkam bog’lanmagan.

Xulosa

SOLID printsiplari dasturning keyinchalik ham to'g'ri va oson ishlashligi uchun muhimdir .

E’tiboringiz uchun rahmat!!! Xato va kamchiliklar uchun uzr.