aidoc.uz

1-qism · Dart asoslari·02-bob

Dart OOP, generiklar va Dart 3 imkoniyatlari

Konstruktorlar, mixinlar, generiklar va Dart 3'ning records/pattern matching/sealed classes imkoniyatlari — arxitektura savollarida so'raladigan mavzular.

Widget daraxti qurish — bu aslida katta miqyosdagi obyektga yo'naltirilgan dizayn mashqi. Flutter'ning o'zi mixinlarga (SingleTickerProviderStateMixin), implicit interfeyslarga va generiklarga (State<T>, ValueNotifier<T>) tayanadi. Senior daraja intervyusida sizdan nafaqat "mixin nima" degan ta'rifni, balki nega Dart bunday tanlov qilgani va bu qanday amaliy oqibatlarga olib kelishini tushuntirish kutiladi. Bu bobda Dart'ning OOP modelini va Dart 3 bilan kelgan zamonaviy imkoniyatlarni ko'rib chiqamiz.

1. Konstruktorlar — oddiy, named, factory va const#

class Foydalanuvchi {
  final String ism;
  final int yosh;
 
  // Asosiy konstruktor — "this.ism" sintaksisi maydonga to'g'ridan-to'g'ri tayinlaydi
  Foydalanuvchi(this.ism, this.yosh);
 
  // Named konstruktor — bir xil klass uchun bir nechta yaratish usuli
  Foydalanuvchi.mehmon() : ism = "Mehmon", yosh = 0;
 
  // Initializer list — konstruktor tanasidan OLDIN bajariladigan hisob-kitoblar
  Foydalanuvchi.validatsiyaBilan(this.ism, int kiritilganYosh)
      : yosh = kiritilganYosh < 0 ? 0 : kiritilganYosh;
 
  // const konstruktor — compile-time'da o'zgarmas obyekt yaratish imkonini beradi
  const Foydalanuvchi.bosh() : ism = "", yosh = 0;
}
 
// Factory konstruktor — har doim YANGI obyekt yaratishga majbur EMAS
class Ulanish {
  static Ulanish? _instance;
  final String host;
 
  Ulanish._ichki(this.host);   // private nomlangan konstruktor
 
  factory Ulanish(String host) {
    return _instance ??= Ulanish._ichki(host);   // singleton naqshi
  }
}

Nega factory kerak? Oddiy konstruktor har doim yangi obyekt yaratadi va super() chaqirishga majbur. factory konstruktor esa oddiy statik metodga o'xshaydi — u keshdan mavjud obyektni qaytarishi, subklass obyektini qaytarishi (masalan, JSON tipiga qarab turli klass yaratadigan fromJson) yoki null qaytarishi (agar qaytarish tipi nullable bo'lsa) mumkin. Bu ayniqsa singleton naqshi va polimorfik deserializatsiya uchun keng qo'llaniladi.

const konstruktor va widget performance: Flutter'da const Text("Salom") yozilganda, Dart bu obyektni compile-time'da yaratadi va butun dastur davomida bitta xotira manzilida qayta ishlatadi. Bu shuni anglatadiki, build() qayta chaqirilganda, agar widget daraxtining shu qismi o'zgarmagan bo'lsa, Flutter == solishtiruvi orqali (const obyektlar avtomatik ravishda qiymat bo'yicha teng bo'ladi) uni butunlay qayta qurishni o'tkazib yuboradi — bu eng arzon performance optimallashtirishlaridan biri.

2. Abstract klasslar va implicit interfeyslar#

Dart'da alohida interface kalit so'zi yo'q — har bir klass avtomatik ravishda o'zining implicit interfeysini hosil qiladi.

abstract class Hayvon {
  void ovozChiqar();               // abstrakt metod — tanasi yo'q
  String get turi => "noma'lum";   // konkret implementatsiyaga ega bo'lishi HAM mumkin
}
 
class Mushuk implements Hayvon {   // implements — FAQAT interfeysni (signaturani) oladi
  @override
  void ovozChiqar() => print("Miyov");
 
  @override
  String get turi => "mushuk";     // implements'da HAMMA metod/getter'ni qayta yozish SHART
}
 
class It extends Hayvon {          // extends — implementatsiyani HAM meros qiladi
  @override
  void ovozChiqar() => print("Vov");
  // turi getter'ini qayta yozish shart EMAS — Hayvon'dan meros oladi
}

extends vs implements — eng ko'p chalkashadigan farq: extends bilan siz ota klassning konkret implementatsiyasini ham meros qilasiz (faqat bitta ota klass bo'lishi mumkin — yagona meros). implements bilan esa siz faqat shaklni (signature) olasiz, implementatsiyani emas — shuning uchun har bir metodni qaytadan yozishga majbursiz, lekin buning evaziga bir klass bir nechta interfeysni implement qila oladi:

class Robot implements Hayvon, Zaryadlanuvchi {   // ko'plab interfeys — ruxsat etiladi
  @override
  void ovozChiqar() => print("Bip-bip");
  @override
  String get turi => "robot";
  @override
  void zaryadla() => print("Zaryadlanmoqda...");
}

3. Mixinlar — "ko'plab meros"ning Dart'cha yechimi#

mixin Suzuvchi {
  void suz() => print("Suzyapman");
}
 
mixin Uchuvchi {
  void uch() => print("Uchyapman");
}
 
class Ordak extends Hayvon with Suzuvchi, Uchuvchi {
  @override
  void ovozChiqar() => print("Vaq-vaq");
}
 
final ordak = Ordak();
ordak.suz();   // Suzuvchi mixinidan
ordak.uch();   // Uchuvchi mixinidan

Mixin — bu klass bo'lmagan, lekin klassga "qo'shib qo'yiladigan" funksionallik bo'lagi. extends bitta ota klassga cheklangani sababli, Dart bir nechta manbadan xatti-harakatni "aralashtirish" uchun withni taklif qiladi. Flutter'da eng ko'p ko'rinadigan misol — SingleTickerProviderStateMixin, u AnimationController uchun kerak bo'lgan vsync funksionalligini State klassiga qo'shib beradi, State klassining o'zi esa hech narsani meros qilmaydi.

on cheklovi: mixin faqat ma'lum bir klass turiga qo'llanilishini majburlash mumkin:

mixin Uchuvchi on Hayvon {   // faqat Hayvon (yoki uning subklassi) bilan ishlatilishi mumkin
  void uch() => print("$runtimeType uchyapti");
}

4. Extension methods — mavjud tiplarga funksionallik qo'shish#

extension StringKengaytmasi on String {
  bool get isValidEmail => RegExp(r'^[\w.+-]+@[\w-]+\.[\w.-]+$').hasMatch(this);
 
  String get boshHarfKatta =>
      isEmpty ? this : this[0].toUpperCase() + substring(1);
}
 
print("aziz".boshHarfKatta);            // "Aziz"
print("test@mail.com".isValidEmail);    // true

Extension'lar mavjud klassni (hatto String yoki int kabi Dart'ning o'z built-in klasslarini) o'zgartirmasdan, ustiga yangi metod/getter "qo'shib qo'yish" imkonini beradi. Bu — kompozitsiya orqali kengaytirish, meros orqali emas: runtime'da hech qanday qo'shimcha obyekt yaratilmaydi, extension metodi shunchaki compile-time'da statik funksiya chaqiruviga aylantiriladi, shuning uchun performance narxi yo'q.

5. Generiklar va chegaralangan tip parametrlari#

class Konteyner<T> {
  final T qiymat;
  Konteyner(this.qiymat);
 
  T ol() => qiymat;
}
 
final sonKonteyner = Konteyner<int>(42);
final matnKonteyner = Konteyner<String>("salom");
 
// Chegaralangan generik — T faqat Comparable bo'lgan tiplar bo'lishi mumkin
T katta<T extends Comparable<T>>(T a, T b) => a.compareTo(b) > 0 ? a : b;
 
print(katta(5, 10));          // 10
print(katta("olma", "nok"));  // "olma" (alifbo tartibida "o" > "n" emas — natija stringlarga bog'liq)

Generiklar Flutter kodida hamma joyda: State<MyWidget>, ValueNotifier<int>, Future<List<User>>, StreamBuilder<AuthState>. Ularning asosiy vazifasi — runtime tip tekshiruvi va castingdan qochish, buning o'rniga compiler'ga tiplarni "bir marta yozib, ko'p joyda xavfsiz qayta ishlatish" imkonini berish. Chegaralangan generik (T extends Comparable<T>) esa "bu funksiya faqat ma'lum imkoniyatlarga ega tiplar bilan ishlaydi" degan kontraktni compiler darajasida ta'minlaydi — masalan, katta() funksiyasi ichida .compareTo() chaqirilishi mumkinligini kafolatlaydi.

6. Dart 3: Records — nomsiz, yengil ma'lumot tuzilmalari#

// Positional record
(String, int) foydalanuvchiMalumoti() => ("Aziz", 28);
 
final (ism, yosh) = foydalanuvchiMalumoti();   // destructuring
print("$ism, $yosh yosh");
 
// Named fields bilan record
({String ism, int yosh}) namedRecord() => (ism: "Dilnoza", yosh: 25);
 
final natija = namedRecord();
print(natija.ism);   // maydon nomi orqali kirish

Records klass yozmasdan turib bir nechta qiymatni birgalikda qaytarish imkonini beradi — avval buning uchun alohida "DTO" klass yozish yoki Map<String, dynamic> (tip xavfsizligini yo'qotib) ishlatish kerak edi. Records qiymat bo'yicha teng (== avtomatik ishlaydi, hashCode ham avtomatik generatsiya qilinadi) — bu ularni klasslardan ustun qiladigan yana bir jihat, chunki klasslarda buni qo'lda yozish kerak edi (1-bobda ko'rganimizdek).

7. Pattern matching va exhaustive switch#

sealed class TarmoqHolati {}
class Yuklanmoqda extends TarmoqHolati {}
class Muvaffaqiyat extends TarmoqHolati {
  final List<String> malumot;
  Muvaffaqiyat(this.malumot);
}
class Xato extends TarmoqHolati {
  final String xabar;
  Xato(this.xabar);
}
 
String holatniKorsat(TarmoqHolati holat) => switch (holat) {
  Yuklanmoqda() => "Yuklanmoqda...",
  Muvaffaqiyat(malumot: var m) => "${m.length} ta element topildi",
  Xato(xabar: var x) => "Xatolik: $x",
  // Boshqa hech qanday holat yo'q deb — else/default SHART EMAS!
};

Nima uchun sealed class bu yerda muhim? TarmoqHolati sealed deb belgilanganda, Dart compiler uning barcha subklasslari shu faylda e'lon qilinganini biladi va aynan shuning uchun switch ifodasida barcha holatlar qamrab olinganligini (exhaustiveness) compile-time'da tekshira oladi. Agar kelajakda TarmoqHolatiga yangi subklass (masalan, Bekor Qilindi) qo'shsangiz, uni qamrab olmagan har bir switch compile xatosi beradi — bu "yangi holatni unutib qoldirish" degan butun bir xato turkumini yo'qqa chiqaradi. Bu naqsh ayniqsa Bloc/Riverpod state klasslarida keng qo'llaniladi.

Destructuring pattern'lar#

final foydalanuvchi = {"ism": "Aziz", "manzil": {"shahar": "Toshkent"}};
 
// Map pattern bilan chuqur destructuring
if (foydalanuvchi case {"ism": String ism, "manzil": {"shahar": String shahar}}) {
  print("$ism, $shahar shahridan");
}
 
// Object pattern — klass maydonlarini destructure qilish
switch (Muvaffaqiyat(["a", "b"])) {
  case Muvaffaqiyat(malumot: [var birinchi, ...]):
    print("Birinchi element: $birinchi");
  default:
    print("Boshqa holat");
}

8. Ko'p uchraydigan xato javoblar#

SavolXato (junior) javobTo'g'ri (senior) javob
"extends va implements farqi?""Ikkalasi ham meros qiladi"extends implementatsiyani ham meros qiladi, implements faqat signaturani — har bir metodni qayta yozish shart
"Mixin nima uchun kerak?""Bir nechta klassdan meros olish uchun"Dart'da ko'plab meros yo'q — mixin funksionallikni "aralashtirish" orqali shunga o'xshash natijaga erishadi, lekin klass emas
"sealed class nima beradi?""Xavfsizroq bo'ladi"Compiler switchda barcha subklasslar qamrab olinganini tekshiradi — yangi holat qo'shilganda unutilgan joylarni compile-time'da topadi
"Records klassdan nimasi bilan farq qiladi?""Qisqaroq yozish usuli, xolos"Avtomatik qiymat bo'yicha tenglik va hashCode, klass e'lon qilmasdan bir nechta qiymatni birlashtirish — lekin metod/xatti-harakat saqlay olmaydi

Tez-tez so'raladigan intervyu savollari#

Savol: factory konstruktor oddiy konstruktordan nimasi bilan farq qiladi va qachon ishlatiladi?

Javob: Oddiy konstruktor har doim yangi obyekt yaratadi. factory konstruktor esa "statik metod"ga o'xshaydi — obyekt yaratish mantig'ini to'liq nazorat qiladi: keshdan qaytarishi (singleton), tashqi parametrga qarab turli subklass qaytarishi (polimorfik fromJson) yoki hisob-kitob asosida obyekt yaratishi mumkin. Eng tipik qo'llanilishi — JSON'dan klassni deserializatsiya qilganda, kelayotgan type maydoniga qarab mos subklass obyektini qaytarish.

Savol: Mixin bilan abstract klass orasidagi amaliy farqni tushuntiring.

Javob: Ikkalasi ham to'liq implementatsiya bera oladi, lekin mixin konstruktorga ega bo'la olmaydi va mustaqil instansiyalanmaydi — u faqat with orqali boshqa klassga "qo'shib qo'yiladi". Bundan tashqari, bitta klass bir nechta mixin bilan with qila oladi (class X extends Y with A, B, C), lekin faqat bitta klassdan extends qila oladi. Amaliy qoida: agar bu "narsa" (is-a munosabat) bo'lsa — meros/interfeys; agar bu "qobiliyat" (can-do, masalan Uchuvchi, Solishtiriluvchi) bo'lsa — mixin.

Savol: sealed class va oddiy abstract class orasidagi farq nima, va bu farq nega Bloc/Riverpod kabi state management kutubxonalarida muhim?

Javob: Oddiy abstract classning subklasslari istalgan faylda, istalgan paketda yaratilishi mumkin — compiler ularning to'liq ro'yxatini bila olmaydi. sealed class esa barcha to'g'ridan-to'g'ri subklasslari bitta faylda (yoki bitta bibliotekada) e'lon qilinishini talab qiladi, shu orqali compiler ularning to'liq, chekli ro'yxatini biladi. Bu switch ifodalarida exhaustiveness tekshiruvini yoqadi: state klassi sifatida sealed class ishlatilganda, yangi state (masalan, "Bekor qilindi") qo'shilganda, uni qayta ishlamagan har bir switch (masalan, UI'ni state'ga qarab render qiluvchi joy) compile xatosi beradi — runtime'da "handle qilinmagan holat" xatosi o'rniga.

Savol: Records'ni qachon oddiy klass o'rniga, qachon klassni records o'rniga ishlatasiz?

Javob: Records — vaqtinchalik, funksiya ichida yoki funksiyalar o'rtasida bir necha qiymatni birga tashish uchun (masalan, bir nechta qiymat qaytaradigan funksiya), ayniqsa alohida DTO klass yaratish ortiqcha bo'lgan holatlarda qulay. Klass esa xatti-harakat (metodlar), meros, private holat yoki domenning "haqiqiy" tushunchasi (masalan, User, Order) bo'lganda kerak. Amaliy qoida: agar tuzilma faqat ma'lumot saqlaydigan bo'lsa va qisqa umr ko'rsa — record; agar u domen modeli bo'lib, kodning ko'p joyida ishlatilsa va o'z mantig'iga ega bo'lsa — klass.

Savol: Extension methods runtime'da qanday ishlaydi — performance narxi bormi?

Javob: Yo'q — extension metodlari compile-time'da statik funksiya chaqiruviga aylantiriladi ("aziz".boshHarfKatta aslida StringKengaytmasi.boshHarfKatta("aziz") ga o'xshash narsaga transformatsiya qilinadi). Hech qanday runtime obyekt yaratilmaydi, asl klass o'zgartirilmaydi (monkey-patching emas), va bir xil tip uchun bir nechta extension bo'lsa, chaqiruv joyida qaysi extension import qilinganiga qarab compiler qaysi birini ishlatishni hal qiladi (agar konflikt bo'lsa — aniq as bilan import qilib ajratish kerak).

Savol: implements ishlatilganda nega barcha metodlarni qayta yozish shart, extendsda esa shart emas?

Javob: Chunki implements faqat klassning implicit interfeysini (uning shakli/signaturasini) oladi, implementatsiyasini emas — bu Java'dagi interfacega o'xshaydi, faqat Dart'da har bir klass avtomatik ravishda o'z interfeysini hosil qiladi, alohida kalit so'z kerak emas. extends esa to'liq implementatsiyani meros qiladi, shuning uchun override qilinmagan metodlar ota klassdagidek ishlayveradi.

Savol: Generik funksiyada T extends Comparable<T> kabi cheklov (bound) nima uchun kerak?

Javob: Cheklovsiz generik T haqida compiler hech narsa bilmaydi — T ustida faqat Objectga xos umumiy metodlarni (toString(), ==) chaqirish mumkin. T extends Comparable<T> yozilganda, compiler T o'rnida faqat Comparable interfeysini implement qilgan tiplar kelishini biladi va shuning uchun funksiya tanasida .compareTo() metodini xavfsiz chaqirish mumkin bo'ladi — bu runtime tip tekshiruvisiz, compile-time'da tekshiriladigan kontrakt.

Savol: const konstruktor bilan yaratilgan ikkita "bir xil" obyekt xotirada bitta obyektmi, ikkitami?

Javob: Agar ikkalasi ham const ifoda sifatida, bir xil argumentlar bilan yaratilgan bo'lsa (masalan, const Point(1, 2) ikki marta) — Dart ularni canonicalize qiladi, ya'ni ikkalasi ham xotirada bitta obyektga ishora qiladi. Bu shuni anglatadiki, identical(a, b) ham true qaytaradi, == haqida gapirmasa ham bo'ladi. Flutter'da bu const Text("Yuklanmoqda") kabi widget'lar uchun muhim — bir xil joyda takroran qurilsa ham, Flutter darhol "bu xuddi avvalgi widget" deb tanib, qayta qurishni o'tkazib yuboradi.

Savol: Pattern matching'dagi switch ifodasi (switch expression) klassik switch operatoridan (switch statement) nimasi bilan farq qiladi?

Javob: Klassik switch — bu operator (statement): har bir case ichida break/return kerak, va u qiymat qaytarmaydi, faqat yon ta'sirlarni (side effects) bajaradi. switch ifodasi (Dart 3) esa qiymat qaytaradi — => sintaksisi bilan, va break kerak emas. Muhimi, switch ifodasi bilan pattern matching (destructuring, sealed klasslar bilan exhaustiveness tekshiruvi) birga ishlatilganda, kod ancha qisqa va compiler tomonidan kuchliroq tekshiriladigan bo'ladi — bu zamonaviy Dart'da state'ga qarab UI qurishning tavsiya etiladigan usuli.

Amaliy topshiriq

Sealed class bilan natija (Result) turini modellashtirish

Generik sealed class Natija<T> yarating: Muvaffaqiyat<T> (bitta T qiymat maydoni bilan) va Xatolik (bitta String xabar maydoni bilan) subklasslari bilan. switch ifodasi orqali Natija<T> obyektini Stringga aylantiruvchi funksiya yozing (exhaustive, default case'siz). Keyin Natijaga uchinchi subklass (Yuklanmoqda) qo'shib, funksiyangizni o'zgartirmasdan compile xatosi chiqishini kuzating — bu sealed class'ning asosiy foydasi.

Maslahatni ko'rsatish

sealed class Natija<T> yarating, uni Muvaffaqiyat<T> va Xatolik ga bo'ling, so'ngra switch ifodasi bilan ikkalasini ham qamrab oluvchi funksiya yozing — bitta holatni ataylab o'chirib, compiler xatosini kuzating.