ما هي المساواة على اساس البيانات Data Equality ؟
لماذا نحتاج دوال مثل (copyWith) و (toString)؟
هل حاولت من قبل ان تقارن بين كائنين من الكلاس بنفس القيم والنتيجة كانت انهما غير متساويين؟
او انك اردت ان تغير حقل واحد من الكائن وتترك باقي الحقول كما هي من دون ان اعادة اسناد القيم لها؟
او في شاشة الكونسول عندما تريد ان تطبع كلاس بصيفة ومعلومات واضحة وليس فقط "instance of class"؟
مصطلحات :
الكائن =object
تخطي الدالة =method overriding
حسنا لنقم بتجزئة هذه الحالات
المساوات على اساس القيم (value Equality)
لنفرض ان لدينا كلاس اسمه (Rectangle) يحتوي على حقول مثل (width) ,(height)
للتبسيط.لنقل انك انشات كائنان من هذا الكلاس بنفس القيم واردت ان تقارن بينهما .يجب ام يكونا متساويين صحيح؟
في الحقيقة هما غير متساويين ,لكن لماذا ؟؟
عملية المساواه لا تقارن حقول الكلاس وانما تقارن الكلاس بشكل عام ولان الكائنان الذان قمنا بمقارنتهما هما بمكانين مختلفين في الذاكرة -hashcodes مختلفين - فان الكومبايلر يفترض انهما ليسا متشابهين.
ماذا نفعل؟
لغة الدارت تعطينا الامكانية ان نتخطى (override) دالتين هما دالة المساواة (==) ودالة (hashcode)
عندما نتخطى دالة المساوات يجب ان نجعلها تقارن القيم بين الكائنين ,بعدها يجب ان نتخطى دالة (hashcode)
لكن ما هو ال hashcode ؟
هو رقم تعريفي مميز لكل كائن في الذاكرة وهو رقم صحيح ,يعني ان لكل كائن هاش كود خاصاً به . يمكنك قرائة شرح الموضوع من الموقع الرسمي
هذا الجزء ماخوذ من الموقع الرسمي :
The default hash code implementation provides an identity hash — two objects generally only have the same hash code if they are the exact same object. Likewise, the default behavior for == is identity. If you are overriding ==, it implies you may have different objects that are considered “equal” by your class. Any two objects that are equal must have the same hash code.
ما معناه :
الوضع الافتراضي لدالة (hashcode) هي مقارنة الهوية , اذا كان هناك كائنين لهما نفس الهاش كود فهذا يعني انهما نفس الكائن وهكذا الحال بالنسبة لدالة المساواة فان الوضع الافتراضي لها هو مساواة الهوية واذا تخطيتها فهو يفرض انك كائنين مختلفين تريد انتعتبرهما متساويين .كائنين يكونا متساويين اذا كان لهما نفس ال hashcode .
والان كيف يمكن ان نتخطى تلك الدالتين في مثالنا ؟
بتخطي علامة المساواة (==) هنا نتحقق ما اذا كان الطول والعرض للكائنين متشابهان ام لا
وعند تخطي المساواة يجب تخطي دالة hashcode وبذلك نجعل الكائنين لنفس ال hashcode اي فعليا متساويين . ولهذا اصبحت النتيجة "متساويان " الان
دالة (copyWith)
في حالة ان لدينا كلاس Rectangle واردنا ان ننشئ منه كائن بقيم لنقل الطول ٢٠ وعرض ٣٠ وبعدها اردت ان تحدث قيمة العرض الى ٤٠ .يمكن ان نحدثة بطريقة مباشرة باسناد قيمة جديدة كما في المثال التالي
هذي هي الطريقة المتغيرة (mutable) لتحديث البيانات حيت ان التحديث يكون مباشر على الكائن
في Flutter هذه ليست الطريقة المعتمدة لتغيير البيانات ,هناك طريقة افضل لتغيير بيانات الكائن وذلك بعمل نسخة منه واجراء التغيير على هذه النسخة .هذه الطريقة تدعى الطريقة الثابتة (immutability) عندما تبحث عن هذه الطريقة ستجد الكثير من المصادر تتحث عن ميزاتها
يمكننا ان نمنع اجراء التغيير على حقول الكلاس وذلك بجعلها قيم Final لتحقيق طريقة ال immutable
هل لاحضت ضهور الخطا بعد ان تحولت حقول الكلاس الى قيم Final لان هذه الكلمة المفتاحية تلزم عدم اسناد قيمة اخرى بعد اسناد القيمة الاولية واذا حاولنا ان نغيرها لا يسمح لنا فيظهر لنا خطا .والحل هو عمل نسخة من الكائن واجراء التعديل فيها
في وضعنا الحالي الكلاس Rectangle لديه حقلين فقط لكن ماذا او كان لدينا كلاس يحوي عشرة حقول او اكثر وكنت تريد تغيير قيمة احدى هذه الحقول ,ليس من المنطقي نسخ كل هذه القيم القديمة في الكائن الجديد اليس كذلك ؟
بالطبع, هناك طريقة لحل هذه المشكلة وهي اضافة دالة (copyWith) الى الكلاس
هذه الدالة تقبل جميع المتغيرات التي يقبلها ال constructor لكن بحالة قابلة لقيمة نل nullable type ,وعملها هو اعطاء القيم الى ال constructor وفي حالة كان المتغير بقيمة نل -عندما نريد ان نبقي على القيمة القديمة - تقوم الدالة باعطاء ال constructor نسخة من القيمة القديمة للمتغير
في الدالة main لدينا الكائن (UpdatedRectangle) ياخذ قيمة جديدة للطول اما العرض فلاننا لم نعطه قيمة او بالاحرى اعطيناه قيمة نل فقام بنسخة من (Rectangle1) وذلك بفضل دالة (copyWith).وبهذا لسنا مضطرين لكتابة الكثير من الاكواد
هذه الدالة شائعة في مكتبات Flutter يفترض انك استخدمتها او اطلعت عليها عند استخدام (Theme) وغيره
دالة (toString)
عندما تنشئ كائن من كلاس (Rectangle) بعدها تحاول طباعة هذا الكائن سوف ترى في لوحة ال console انه لم يطبع محتويات الكلاس وانما طبع "instance of class" وهي رسالة لا تعطينا معلومات مفيدة صحيح
اذا كيف نطبع الكلاس بالطريقة التي نريدها بحيث تعطينا معلومات اكثر ؟
هذا هو عمل دالة (toString) حيث ترجع هذه الدالة صيغة نصية من الكلاس اي انها ترجع نص يعرض تفاصيل الكلاس بالطريقة التي نرغب بها
بعد تخطي هذه الدالة نرى ان ال console قام بطباعة النص الذي قمنا بارجاعه منها حاويا لمحتويات الكلاس
وفي النهاية اصبح لدينا الكلاس (Rectangle) لا يحتوي على حقول و constructor فقط وانما يتخطى دوال مثل ==,hashcode ,toString بالاضافة الى دالة copyWith انت الان تعتقد ان هذا كثير بالنسبة لكلاس صغير كالذي لدينا .بالطبع خاصة لو كان الكلاس اكبر واعقد سوف تزداد الامور صعوبة اثناء عملية الكتابة والصيانة والاختباروسيكون الامر ممل وروتيني.
في اللغات الاخرى مثل #C و swift هناك ما يسمى struct اما في لغة Kotlin يوجد data class ومن وضائفهما عمل هذا الكود نيابة عنك فلست مضطرا لكتابة هذه الدوال
لغة دارت لا تمتلك هذه الخاصية لكن لحسن الحظ ان هناك الكثير من المكتبات في موقع وجدت لهذا الغرض اي تجنبك كتابة هذه الاكواد واحدة من هذه المكاتب هي (Freezed) التي تستخدم الية توليد الكود لكتابة هذه الدوال بدلا من كتابتها يدويا وتحتوى هذه المكتبة ميزات اضافية اخرى. يمكنك الاطلاع على تفاصيها في هذا الرابط