الكلاسات في Kotlin: class و data class والوراثة
تطبيقات أندرويد مليئة بـ «أشياء»: مستخدم، ورسالة، ومنتج، وطلب. وكل شيء منها له بيانات (اسم، سعر) وسلوك (يرسل، يُحذف). الطريقة المنظمة لتمثيل هذه الأشياء هي الكلاسات. وفي Kotlin، كتابة الكلاسات أقصر بكثير من Java، وفيها ميزة رائعة اسمها data class ستوفر عليك كوداً كثيراً.
أول كلاس
Section titled “أول كلاس”class Student(val name: String, var age: Int) { fun introduce() { println("أنا $name وعمري $age سنة") }}
fun main() { val s1 = Student("سارة", 14) val s2 = Student("عمر", 15) s1.introduce() s2.introduce() s2.age = 16 println(s2.age)}أنا سارة وعمري 14 سنةأنا عمر وعمري 15 سنة16السطر الأول وحده يفعل الكثير:
class Student: تعريف الكلاس، واسمه بحرف كبير.(val name: String, var age: Int): المُنشئ الأساسي (Primary Constructor)، وهو في نفس الوقت يعرّف الخصائص.valخاصية للقراءة فقط، وvarقابلة للتعديل.- لإنشاء كائن تكتب اسم الكلاس مع القيم:
Student("سارة", 14). لا توجد كلمةnewفي Kotlin.
الكلاس مثل قالب الكعك، وكل كائن كعكة مستقلة: تغيير عمر عمر لم يغيّر عمر سارة.
التحقق من البيانات: init
Section titled “التحقق من البيانات: init”الكتلة init تُنفَّذ عند إنشاء كل كائن، وهي مكان مناسب للتحقق من صحة البيانات:
class Product(val name: String, val price: Double, val quantity: Int = 1) { val total: Double get() = price * quantity
init { require(price >= 0) { "السعر لا يمكن أن يكون سالباً" } }}
fun main() { val p = Product("كتاب", 60.0, 3) println(p.total) println(Product("قلم", 5.0).total) try { Product("خطأ", -1.0) } catch (e: IllegalArgumentException) { println(e.message) }}180.05.0السعر لا يمكن أن يكون سالباًrequire(شرط) { رسالة }: إذا كان الشرط خطأ، يرمي استثناءًIllegalArgumentExceptionبالرسالة، فلا يُنشأ منتج بسعر سالب أبداً.get() = ...: خاصية محسوبة تُحسب كلما قرأتها، ولا تُخزَّن.- المعامل
quantityله قيمة افتراضية، تماماً مثل الدوال.
حماية البيانات: private set
Section titled “حماية البيانات: private set”أحياناً تريد أن يقرأ الجميع الخاصية، لكن لا يغيّرها إلا الكلاس نفسه:
class BankAccount(val owner: String) { var balance = 0.0 private set
fun deposit(amount: Double) { if (amount <= 0) { println("المبلغ يجب أن يكون أكبر من صفر") return } balance += amount }}
fun main() { val acc = BankAccount("منى") acc.deposit(500.0) acc.deposit(-20.0) println(acc.balance)}المبلغ يجب أن يكون أكبر من صفر500.0private set تعني: القراءة متاحة للجميع، أما التعديل فمن داخل الكلاس فقط. لو كتبت خارجه acc.balance = 1000000.0 سيرفض المترجم. والطريقة الوحيدة لزيادة الرصيد هي deposit التي تتحقق من المبلغ. هذا هو التغليف (Encapsulation).
data class: كلاس البيانات
Section titled “data class: كلاس البيانات”كثير من الكلاسات وظيفتها حمل البيانات فقط، مثل كتاب أو رسالة. لهذه الكلاسات أضف كلمة data:
data class Book(val title: String, val pages: Int)
fun main() { val b1 = Book("البرمجة للمبتدئين", 250) val b2 = Book("البرمجة للمبتدئين", 250) println(b1) println(b1 == b2) val b3 = b1.copy(pages = 300) println(b3)}Book(title=البرمجة للمبتدئين, pages=250)trueBook(title=البرمجة للمبتدئين, pages=300)بكلمة واحدة حصلت على ثلاث هدايا:
- طباعة مفهومة: بدلاً من شيء مثل
Student@77459877الذي يُطبع للكلاس العادي. - مقارنة بالمحتوى: كتابان بنفس البيانات متساويان بـ
==. copy: نسخة جديدة مع تعديل خاصية أو أكثر، والأصل يبقى كما هو.
في تطبيقات أندرويد ستستخدم data class لكل البيانات القادمة من الإنترنت أو قاعدة البيانات.
الوراثة: open و override
Section titled “الوراثة: open و override”في Kotlin، الكلاسات مغلقة افتراضياً: لا يمكن الوراثة منها إلا إذا سمحت بذلك بكلمة open:
open class Animal(val name: String) { open fun speak() { println("$name يصدر صوتاً") }}
class Cat(name: String) : Animal(name) { override fun speak() { println("$name يقول: مياو") }}
fun main() { val animals = listOf(Animal("الحصان"), Cat("مشمش")) for (a in animals) a.speak()}الحصان يصدر صوتاًمشمش يقول: مياوopen class: تسمح بالوراثة من الكلاس، وopen fun: تسمح بإعادة كتابة الدالة.: Animal(name): القطة ترث من الحيوان، وتمرر الاسم لمُنشئه.override: إعادة كتابة الدالة الموروثة بسلوك خاص.
لماذا الإغلاق افتراضياً؟ لأن الوراثة غير المخطط لها تسبب أخطاء صعبة. Kotlin تجبرك على التفكير: هل صممت هذا الكلاس فعلاً ليُورَث؟
جرّب بنفسك
Section titled “جرّب بنفسك”- اكتب
data class Courseفيها: الاسم، وعدد الدروس، والحالةisFreeبقيمة افتراضيةtrue. - أنشئ قائمة من 3 كورسات، واطبعها.
- استخدم
filterلتطبع الكورسات التي دروسها أكثر من 8. - تحدٍّ: اكتب
open class Shapeبدالةarea()تُرجع 0.0، وكلاسين يرثان منها:CircleوRectangle، كل منهما يعيد كتابةarea. ثم اطبع مساحات قائمة أشكال مختلفة.
أخطاء شائعة
Section titled “أخطاء شائعة”- الوراثة من كلاس غير open: خطأ
This type is final, so it cannot be extended. أضفopen. - نسيان override: إعادة كتابة دالة موروثة بدون
overrideيرفضها المترجم. - استخدام class عادي لحمل البيانات: تفقد الطباعة المفهومة والمقارنة و
copy. استخدمdata class.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
ما الفائدة الأساسية من data class؟
data class تولّد تلقائياً toString و equals و copy وغيرها، فتوفر عليك كوداً كثيراً.
سؤال
لماذا يرفض المترجم class Dog : Animal() إذا كان Animal معرّفاً بـ class Animal؟
الكلاسات في Kotlin نهائية افتراضياً، ويجب تعليم الكلاس الأب بـ open ليُسمح بالوراثة.
الخلاصة
Section titled “الخلاصة”class Student(val name: String, var age: Int)يعرّف الكلاس وخصائصه ومُنشئه في سطر واحد.initوrequireللتحقق من البيانات، وget()للخصائص المحسوبة.private setيسمح بالقراءة ويحمي التعديل من خارج الكلاس.data classلحمل البيانات: طباعة مفهومة، ومقارنة بالمحتوى، وcopy.- الوراثة تحتاج
openفي الأب، وoverrideلإعادة كتابة الدوال.