فئة البيانات (Data class) في Kotlin: لماذا لا يمكن توريثها وكيفية تجاوز ذلك

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

فئة البيانات (Data class) في Kotlin: لماذا لا يمكن توريثها وكيفية تجاوز ذلك (تحليل تحت الغطاء)

\n\n

هل سبق لك أن كتبت data class User(val name: String)، ثم أردت إنشاء data class Admin extends User؟ وحصلت على خطأ أحمر من المترجم؟ إذا كان الأمر كذلك، فأنت لست وحدك. سؤال «Extend data class in Kotlin» هو من بين الأسئلة الأكثر شيوعًا على Stack Overflow (أكثر من 50,000 مشاهدة).

\n\n

اليوم لن نقدم لك إجابة جاهزة فقط. سننظر تحت غطاء Kotlin، ونشرح لماذا فرض مبتكرو اللغة هذا القيد «القاسي»، ونعرض 3 طرق قانونية لتجاوزه دون كسر البنية.

\n\n

المقالة مكتوبة لبوابة التعليم PythonLib – هنا نحب التعمق، وليس مجرد نسخ مقتطفات برمجية.

\n\n

لماذا لا يمكن توريث فئة البيانات (data class)؟ نظرة على البايت كود

\n\n

لنبدأ مباشرة. لنكتب فئة بيانات بسيطة ونرى ما يولده المترجم:

\n\n
data class User(val name: String, val age: Int)
\n\n

تحت الغطاء، يقوم Kotlin بإنشاء:

\n
    \n
  • equals() – لمقارنة جميع الحقول
  • \n
  • hashCode() – تجزئة بناءً على جميع الحقول
  • \n
  • toString() – تمثيل نصي جميل
  • \n
  • copy() – دالة نسخ مع إمكانية تغيير الحقول
  • \n
  • componentN() – التفكيك (Destructuring)
  • \n
\n\n

والآن تخيل أنك تريد توريث فئة بيانات:

\n\n
open data class Base(val x: Int)\ndata class Derived(val x: Int, val y: Int) : Base(x) // خطأ في الترجمة!
\n\n

يُصدر Kotlin: «Inheritance of data classes is not allowed». لماذا؟ دعنا نفهم.

\n\n

المشكلة رقم 1: copy() تتعطل. دالة copy() تُنشأ فقط لحقول الفئة الحالية. إذا كان لديك Derived الموروث من Base، فإن copy() في Base لا تعرف عن الحقل y. باستدعاء base.copy()، ستفقد بيانات الوريث.

\n\n

المشكلة رقم 2: equals() و hashCode(). تخيل: Base(1) و Derived(1, 2). هل يجب أن يكونا متساويين؟ إذا كانت الإجابة بنعم – يتم انتهاك التناظر (لأن Derived يحتوي على حقول أكثر). إذا كانت الإجابة لا – يتم انتهاك عقد equals/hashCode. اختار Kotlin الطريق الآمن – منع التوريث.

\n\n

المشكلة رقم 3: componentN(). val (x) = base و val (x, y) = derived – عدد مختلف من المكونات. في حالة التوريث، سينشأ ارتباك.

\n\n

الخلاصة: فئة البيانات (data class) صُممت لحاويات البيانات غير القابلة للتغيير، وليس للتسلسلات الهرمية. هذا قرار واعٍ في تصميم اللغة.

\n\n

الطريقة رقم 1: التركيب (Composition) بدلاً من التوريث (موصى به)

\n\n

أنظف طريقة هي عدم التوريث، بل تضمين فئة بيانات داخل أخرى. هذا يسمى التركيب (Composition).

\n\n
data class Address(val city: String, val street: String)\n\ndata class User(\n    val name: String,\n    val age: Int,\n    val address: Address // تركيب\n)\n\nfun main() {\n    val address = Address("موسكو", "تفيرسكايا")\n    val user = User("آنا", 25, address)\n    println(user) // User(name=آنا, age=25, address=Address(city=موسكو, street=تفيرسكايا))\n    \n    // النسخ يعمل بشكل مثالي\n    val user2 = user.copy(age = 26)\n    println(user2) // User(name=آنا, age=26, address=Address(city=موسكو, street=تفيرسكايا))\n}\n
\n\n

الإيجابيات: كل شيء يعمل «فوريًا»، لا مفاجآت مع equals/hashCode، الكود شفاف.

\n\n

السلبيات: إذا كنت بحاجة إلى مستويات متعددة من التداخل، ستحصل على «دمية ماتريوشكا». ولكن يمكن حل ذلك بتطبيع البيانات.

\n\n

الطريقة رقم 2: الفئة المختومة (Sealed class) + فئة البيانات (data class) (للتسلسلات الهرمية الصارمة)

\n\n

إذا كنت تحتاج حقًا إلى تسلسل هرمي (مثل أنواع مختلفة من المستخدمين)، استخدم sealed class (أو sea

المدونات

توصيات الكتب