Bərabərlik və immutability
Dart-da default == referens bərabərliyidir: iki obyekt yalnız eyni instans olduqda bərabərdir. Ona görə User('Ayan') == User('Ayan') → false.
Üç fərqli anlayışı ayır:
identical(a, b)— eyni obyektdirmi? Referens müqayisəsi; override oluna bilməz.a == b— məntiqi bərabərlik; sinif tərəfindən override oluna bilər.a.hashCode— hash-əsaslı kolleksiyalar (Set,Mapaçarları) üçün tam ədəd.
Müqavilə (contract) sadədir və pozulması səssiz buglara aparır:
a == bdoğrudursa,a.hashCode == b.hashCodemütləq doğru olmalıdırhashCodebərabərdirsə,==bərabər olmaya bilər (kolliziya normaldır)==refleksiv, simmetrik, tranzitiv və sabit olmalıdır
Ən çox edilən səhv: ==-i override edib hashCode-u unutmaq. Kod compile olunur (yalnız lint xəbərdarlığı), amma obyekt Set-də iki dəfə görünür və Map-dən tapılmır — çünki kolleksiya əvvəlcə hash-a baxır.
| Yanaşma | Nə verir | Bədəli |
|---|---|---|
| Default (override yoxdur) | Referens bərabərliyi | Dəyər obyektləri üçün yararsız |
| Əl ilə `==` + `hashCode` | Tam nəzarət | Sahə əlavə edəndə yeniləmə unudulur |
| `Object.hash(a, b, ...)` | Düzgün hash birləşdirmə | Yenə də əl ilə yazılır |
| Record | Struktural bərabərlik pulsuz | Ad və metodlar yoxdur |
| `package:equatable` | Sahə siyahısı ilə avtomatik | Asılılıq, `props` siyahısı əl ilə saxlanılır |
| `freezed` (kod generasiyası) | `==`, `hashCode`, `copyWith`, `toString` | Build runner, generated fayllar |
Value object — məhz dəyəri ilə müəyyən olunan tip: Money(100, 'AZN'), Coordinates(40.4, 49.8), EmailAddress('[email protected]'). Belə tiplər üçün üç şey birlikdə gəlir: ==/hashCode override-ı, immutability (final sahələr), və mümkünsə const konstruktor.
Niyə immutability? Çünki mutable obyektin hashCode-u dəyişə bilər. Obyekt Set-ə əlavə olunub, sonra sahəsi dəyişibsə, o, öz "bucket"-ində qalır və set.contains(obj) `false` qaytarır — obyekt orada olsa belə. Bu, tapılması ən çətin buglardan biridir.
`const` konstruktor əlavə bir üstünlük verir: canonicalisation. const Money(100, 'AZN') ifadəsi proqramda tək obyektdir, ona görə identical(const Money(100, 'AZN'), const Money(100, 'AZN')) → true. Yəni müqayisə == gövdəsini heç işə salmadan referens səviyyəsində həll olunur.
const konstruktor şərtləri: bütün sahələr final olmalı, gövdə boş olmalı, və bütün sahə dəyərləri compile-time sabiti olmalıdır.
`copyWith` pattern-i immutable modellə işləməyin standart üsuludur: obyekti dəyişmək əvəzinə, dəyişmiş nüsxə yaradırsan.
Sadə implementasiyanın məlum problemi var: copyWith({String? name}) imzasında null iki fərqli mənaya gəlir — "bu sahəyə toxunma" və "bu sahəni null et". Nullable sahələr üçün bu ikilik real bugdur. Həlli: sentinel obyekt (Object? name = _unset) və ya freezed paketinin generated copyWith-i.
Flutter niyə immutability-yə söykənir? Səbəb rebuild dövrünün müqayisəyə əsaslanmasıdır: framework köhnə və yeni konfiqurasiyanı müqayisə edib nəyin dəyişdiyini müəyyən edir. Bu müqayisə yalnız o halda ucuz və düzgündür ki, obyektlər dəyişməz olsun — mutable obyekt "eyni referens, fərqli məzmun" vəziyyəti yarada bilir və dəyişiklik itir. const konstruktor isə müqayisəni identical() səviyyəsinə endirir. Eyni məntiq state idarəetməsində də işləyir: yeni state obyektinin köhnədən fərqli olması == ilə müəyyən edilir.
Interview ipucu. "== override etdin, hashCode-u niyə də etməlisən?" — cavabı mexanizmlə ver: "`Set` və `Map` əvvəlcə `hashCode`-a görə bucket tapır, yalnız sonra `==` çağırır. Hash-lər fərqlidirsə, `==` heç çağırılmır — bərabər obyekt Set-də iki dəfə görünür və Map-dən tapılmır." Bu, sırf nəzəri qayda deyil, konkret bugdur və müsahib məhz bunu eşitmək istəyir.
İkinci klassik sual: "== ilə identical() fərqi?" — qısa cavab: "`identical` eyni obyekt olub-olmadığını yoxlayır və override oluna bilmir; `==` məntiqi bərabərlikdir və override oluna bilər. `const` obyektlər canonicalise olunduğu üçün eyni `const` ifadəsi üçün `identical` da `true` verir."
📚 Mənbələr və sənədlər
- Object.operator ==rəsmiapi.dart.dev
- Effective Dart: Designrəsmidart.dev