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 mixinidanMixin — 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); // trueExtension'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 kirishRecords 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#
| Savol | Xato (junior) javob | To'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.
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.