TypeScript'da klasslar va OOP'ni qanday tiplash kerak?

MiddleTypeScript #typescript #classes #oop

Savol

Kirish modifikatorlari (public/private/protected), abstrakt klasslar qanday ishlaydi va implements bilan extends o'rtasidagi farq nimada?

Qisqa javob

public (standart bo'yicha), private va protected klass a'zolarining ko'rinishini boshqaradi: private — faqat klass ichida, protected — klass ichida va merosxo'rlarda, public — hamma joyda. abstract class — to'g'ridan-to'g'ri instansiya qilib bo'lmaydigan, merosxo'rlar uchun kontrakt belgilaydigan klass. extends — bitta klassdan amalga oshirishni meros qilib olish, implements — kod merosxo'rligisiz interfeys strukturasini amalga oshirish majburiyati.

Batafsil javob

Kirish modifikatorlari — faqat kompilyatsiya bosqichidagi tekshiruv; kompilyatsiya qilingan JS'da ular runtime'da kirishni hech qanday cheklamaydi (runtime'da ham maxfiylikni ta'minlaydigan haqiqiy private maydonlar #fielddan farqli o'laroq):

class BankAccount {
  private balance: number
  protected owner: string
  public readonly accountId: string

  constructor(owner: string, initial: number) {
    this.owner = owner
    this.balance = initial
    this.accountId = crypto.randomUUID()
  }

  private validate(amount: number) {
    if (amount <= 0) throw new Error('Invalid amount')
  }

  deposit(amount: number) {
    this.validate(amount) // OK — klass ichida kirish mumkin
    this.balance += amount
  }
}

const acc = new BankAccount('Alice', 100)
// acc.balance // Xato — private tashqaridan mavjud emas
// acc.validate(10) // Xato — private metod

Shuningdek qisqartirilgan sintaksis ham bor — parameter properties, u maydonni to'g'ridan-to'g'ri konstruktorda e'lon qiladi va tiplaydi:

class Point {
  constructor(public x: number, public y: number) {}
}
// x, y maydonlarini e'lon qilish va ularni konstruktor tanasida tayinlashga ekvivalent

abstract class — new orqali to'g'ridan-to'g'ri yaratib bo'lmaydigan bazaviy klass. U ham amalga oshirilgan metodlarni, ham merosxo'rlarda amalga oshirilishi majburiy bo'lgan tanasiz abstract-metodlarni o'z ichiga olishi mumkin:

abstract class Shape {
  abstract area(): number // amalga oshirilmagan — kontrakt

  describe(): string {    // amalga oshirilgan — barcha merosxo'rlar uchun umumiy
    return `Area: ${this.area()}`
  }
}

class Circle extends Shape {
  constructor(private radius: number) { super() }
  area(): number { return Math.PI * this.radius ** 2 }
}

// const s = new Shape() // Xato — abstract klassni instansiya qilib bo'lmaydi

extends vs implements:

  • extends — klassdan (yoki interfeyslar uchun interfeysdan) meros olish. Merosxo'r ota-klassning amalga oshirilishini (maydonlar, metodlar) oladi va uni qayta aniqlashi mumkin, agar ota-klass konstruktorga ega bo'lsa, konstruktorda super()ni chaqirishi shart.
  • implements — klass interfeys strukturasiga (yoki vergul orqali bir nechtasiga) mos kelishga majbur bo'ladi, lekin undan hech qanday kod olmaydi — amalga oshirishni to'liq o'zi yozishi kerak. Bu sof kontraktni kompilyatsiya bosqichida tekshirish.
interface Flyable { fly(): void }
interface Swimmable { swim(): void }

class Duck extends Animal implements Flyable, Swimmable {
  fly() { console.log('Flying') }
  swim() { console.log('Swimming') }
}

Klass bir nechta interfeysni amalga oshirishi mumkin (implements A, B), lekin faqat bitta klassdan meros olishi mumkin (extends — yagona merosxo'rlik).

Misol

abstract class Repository<T extends { id: number }> {
  protected items: T[] = []

  abstract validate(item: T): boolean

  add(item: T): void {
    if (!this.validate(item)) throw new Error('Invalid item')
    this.items.push(item)
  }

  findById(id: number): T | undefined {
    return this.items.find((i) => i.id === id)
  }
}

interface Serializable {
  toJSON(): string
}

interface Product { id: number; name: string; price: number }

class ProductRepository extends Repository<Product> implements Serializable {
  validate(item: Product): boolean {
    return item.price >= 0 && item.name.length > 0
  }

  toJSON(): string {
    return JSON.stringify(this.items)
  }
}

const repo = new ProductRepository()
repo.add({ id: 1, name: 'Book', price: 19.99 })
console.log(repo.toJSON())

Qo'shimcha savollar

  • Klassning haqiqiy private maydonlari (#field) private modifikatoridan nimasi bilan farq qiladi?
  • Abstrakt klassda abstract xususiyatni (metod emas) e'lon qilish mumkinmi?
  • TypeScript strukturaviy tiplashda klasslarning mosligini qanday tekshiradi — private maydonlar ikkita har xil klassning mosligiga ta'sir qiladimi?