Sparround

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 == bməntiqi bərabərlik; sinif tərəfindən override oluna bilər.
  • a.hashCode — hash-əsaslı kolleksiyalar (Set, Map açarları) üçün tam ədəd.

Müqavilə (contract) sadədir və pozulması səssiz buglara aparır:

  • a == b doğrudursa, a.hashCode == b.hashCode mütləq doğru olmalıdır
  • hashCode bə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şmaNə verirBədəli
Default (override yoxdur)Referens bərabərliyiDəyər obyektləri üçün yararsız
Əl ilə `==` + `hashCode`Tam nəzarətSahə əlavə edəndə yeniləmə unudulur
`Object.hash(a, b, ...)`Düzgün hash birləşdirməYenə də əl ilə yazılır
RecordStruktural bərabərlik pulsuzAd və metodlar yoxdur
`package:equatable`Sahə siyahısı ilə avtomatikAsı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