Sparround

Class modifiers: sealed, final, base, interface

Əvvəlcə üç əlaqə növünü ayır — modifierlər məhz bunları icazə verir və ya qadağan edir:

  • extendsimplementasiyanı miras alırsan. Bir dənə super class ola bilər, super.method() işləyir, konstruktorlar zəncirlə çağırılır.
  • implements — yalnız interfeys müqaviləsini götürürsən. Dart-da hər class avtomatik olaraq eyni adlı interfeys yaradır, ona görə istənilən konkret class-ı implements etmək mümkündür; amma bu zaman bütün üzvləri özün yazmalısan, heç nə miras qalmır.
  • withmixin qoşursan: implementasiya gəlir, amma "is-a" iyerarxiyası qurmadan və neçə dənə istəsən.

Dart 3-ə qədər bu azadlıq tam idi: paket istifadəçisi sənin istənilən class-ını extend və ya implement edə bilirdi, sən isə paketdə metod əlavə etsən onların kodu sınırdı. Class modifiers məhz bu problemi həll etmək üçün gəldi.

ModifierKitabxanadan KƏNARDA icazə verirNə üçündür
(modifiersiz) `class`new, extends, implementsDart 2 davranışı — ən sərbəst, ən qorumasız
`abstract class`extends, implements (new yox)Tam olmayan tip — birbaşa instansiya edilə bilməz
`base class`yalnız extends; alt tiplər də base/final/sealed olmalıdırHər instansiyanın sənin implementasiyanı daşımasına zəmanət
`interface class`yalnız implementsMüqavilə vermək, amma implementasiyanı miras verməmək
`final class`nə extends, nə implements — yalnız istifadəGələcəkdə breaking change olmadan üzv əlavə etmək azadlığı
`sealed class`heç nə (implicitly abstract + final)Alt tiplərin tam siyahısı məlum — exhaustive switch
`mixin class`extends, implements və `with`Həm class, həm mixin kimi işlədilə bilən tip

Sealed class — Dart 3-ün əsas yeniliyi. sealed bir class-ı eyni anda abstractfinal edir və əlavə olaraq kompilyatora deyir: bu tipin bütün birbaşa alt tipləri eyni kitabxanadadır.

Bundan iki nəticə çıxır:

  • switch ifadəsi exhaustive yoxlanılır: bütün variantları yazsan default şaxəsi lazım gəlmir.
  • Yeni variant əlavə edəndə default-suz bütün switch-lər kompilyasiya olunmur — kompilyator sənə düzəldiləsi yerlərin siyahısını verir. Bu, run-time xətanı compile-time xətasına çevirməkdir.

Müasir Dart-da Result, LoadState, AuthState kimi modelləri məhz sealed hierarchy + pattern matching ilə yazırlar. Kombinasiyalar da mümkündür və mənalıdır: abstract interface class — saf interfeys idiomu; sealed isə heç nə ilə birləşmir, çünki artıq özündə abstract + final daşıyır.

Kitabxana müəllifi kimi hansını seçmək? Praktik qayda:

  • Daxili (application) kodda çox vaxt heç bir modifier lazım deyil — komanda onsuz da bütün kodu görür.
  • Paket yazırsansa default olaraq `final` seç. Sonradan final-ı götürmək breaking change deyil, əlavə etmək isə breaking change-dir.
  • Yalnız müqavilə vermək istəyirsənsə (istifadəçi öz implementasiyasını yazsın, test-də fake qursun) — abstract interface class.
  • Hər alt tipin sənin state və invariant-larını daşımasına ehtiyac varsa — base.
  • Bağlı variant çoxluğunu modelləşdirirsənsə — sealed.

Diqqət: base "yoluxucu"dur — base class-ın hər alt tipi də base, final və ya sealed olmalıdır. Bu qəsdəndir: implementasiya zəncirinin heç bir yerində kimsə implements ilə boşluq aça bilməsin.

Müsahibə ipucu. İki sual demək olar zəmanətlidir. Birincisi: "`extends` və `implements` fərqi nədir?" — güclü cavabda mütləq de ki, Dart-da hər class implicit interfeys yaradır, ona görə implements konkret class üzərində də işləyir, sadəcə heç bir implementasiya miras qalmır; testdə fake qurmağın ən sadə yolu budur. İkincisi: "sealed class-ı nə vaxt enum-a üstün tutursan?" — cavabı bir cümləyə yığ: variantların hər birinin öz datası varsa sealed, sadəcə adlar siyahısıdırsa enum. Ən çox edilən səhv: sealed-i "final class"-ın sinonimi kimi izah etmək. Fərq exhaustiveness-dədir — final sadəcə qapayır, sealed isə kompilyatora tam siyahını verir.

📚 Mənbələr və sənədlər