‫Kotlin Bootcamp للمبرمجين 4: البرمجة التي تعتمد على العناصر

هذا الدرس العملي حول الترميز هو جزء من دورة Kotlin التدريبية للمبرمجين. يمكنك تحقيق أقصى استفادة من هذه الدورة التدريبية إذا اتبعت ترتيب الخطوات في دروس البرمجة. استنادًا إلى معرفتك، قد تتمكّن من قراءة بعض الأقسام بسرعة. هذه الدورة التدريبية موجّهة للمبرمجين الذين يعرفون لغة برمجة تعتمد على العناصر ويريدون تعلُّم Kotlin.

مقدمة

في هذا الدرس التطبيقي حول الترميز، ستنشئ برنامجًا بلغة Kotlin وتتعرّف على الفئات والكائنات في Kotlin. سيكون معظم هذا المحتوى مألوفًا لك إذا كنت تعرف لغة أخرى موجّهة للكائنات، ولكن لدى Kotlin بعض الاختلافات المهمة لتقليل مقدار الرمز الذي تحتاج إلى كتابته. ستتعرّف أيضًا على الفئات المجردة وتفويض الواجهة.

بدلاً من إنشاء تطبيق نموذجي واحد، تم تصميم الدروس في هذه الدورة التدريبية لتعزيز معرفتك، ولكنها شبه مستقلة عن بعضها البعض حتى تتمكن من تصفّح الأقسام التي تعرفها. ولربطها ببعضها، تستخدم العديد من الأمثلة سمة حوض السمك. إذا أردت الاطّلاع على قصة حوض السمك بالكامل، يمكنك الرجوع إلى دورة Kotlin Bootcamp للمبرمجين على Udacity.

ما يجب معرفته

  • أساسيات Kotlin، بما في ذلك الأنواع والعوامل والتكرار
  • بنية الدوال في Kotlin
  • أساسيات البرمجة التي تعتمد على العناصر
  • أساسيات بيئة التطوير المتكاملة (IDE) مثل IntelliJ IDEA أو "استوديو Android"

أهداف الدورة التعليمية

  • كيفية إنشاء فئات والوصول إلى الخصائص في Kotlin
  • كيفية إنشاء الدوال الإنشائية للفئات واستخدامها في Kotlin
  • كيفية إنشاء فئة فرعية وطريقة عمل الوراثة
  • لمحة عن الفئات المجردة والواجهات وتفويض الواجهات
  • كيفية إنشاء فئات البيانات واستخدامها
  • كيفية استخدام الكائنات الفردية وقيم التعداد والفئات المحكمة

الإجراءات التي ستنفذّها

  • إنشاء فئة تتضمّن سمات
  • إنشاء دالة إنشائية لفئة
  • إنشاء فئة فرعية
  • فحص أمثلة على الفئات المجردة والواجهات
  • إنشاء فئة بيانات بسيطة
  • مزيد من المعلومات حول الكائنات الفردية والتعدادات والفئات المحكمة

من المفترض أنّك على دراية بمصطلحات البرمجة التالية:

  • الفئات هي مخططات للكائنات. على سبيل المثال، فئة Aquarium هي المخطط الأساسي لإنشاء كائن حوض سمك.
  • الكائنات هي مثيلات للفئات، وكائن حوض السمك هو Aquarium واحد.
  • الخصائص هي سمات الفئات، مثل طول Aquarium وعرضه وارتفاعه.
  • الطرق، التي تُعرف أيضًا باسم الدوال الأعضاء، هي وظائف الفئة. الطُرق هي الإجراءات التي يمكنك "تنفيذها" باستخدام الكائن. على سبيل المثال، يمكنك fillWithWater() عنصر Aquarium.
  • الواجهة هي مواصفات يمكن أن تنفّذها الفئة. على سبيل المثال، التنظيف هو سمة مشتركة بين الكائنات الأخرى غير أحواض السمك، ويتم التنظيف بشكل عام بطرق متشابهة للكائنات المختلفة. لذا، يمكنك إنشاء واجهة باسم Clean تحدّد طريقة clean(). يمكن للفئة Aquarium تنفيذ الواجهة Clean لتنظيف حوض السمك باستخدام إسفنجة ناعمة.
  • الحِزم هي طريقة لتجميع الرموز البرمجية ذات الصلة من أجل الحفاظ على تنظيمها أو إنشاء مكتبة من الرموز البرمجية. بعد إنشاء حزمة، يمكنك استيراد محتوى الحزمة إلى ملف آخر وإعادة استخدام الرموز والفئات الموجودة فيها.

في هذه المهمة، ستنشئ حزمة جديدة وفئة تتضمّن بعض الخصائص والدوال.

الخطوة 1: إنشاء حزمة

يمكن أن تساعدك الحِزم في تنظيم الرموز البرمجية.

  1. في لوحة المشروع، ضِمن مشروع Hello Kotlin، انقر بزر الماوس الأيمن على المجلد src.
  2. اختَر جديد > حزمة وأطلِق عليها اسم example.myapp.

الخطوة 2: إنشاء فئة تتضمّن سمات

يتم تحديد الفئات باستخدام الكلمة الرئيسية class، وتبدأ أسماء الفئات اصطلاحًا بحرف كبير.

  1. انقر بزر الماوس الأيمن على حزمة example.myapp.
  2. اختَر جديد > ملف / فئة Kotlin.
  3. ضمن النوع، اختَر الصف، وأدخِل اسم الصف Aquarium. يتضمّن IntelliJ IDEA اسم الحزمة في الملف وينشئ لك فئة Aquarium فارغة.
  4. داخل الفئة Aquarium، حدِّد خصائص var واضبط قيمها للعرض والارتفاع والطول (بالسنتيمتر). تهيئة السمات بالقيم التلقائية
package example.myapp

class Aquarium {
    var width: Int = 20
    var height: Int = 40
    var length: Int = 100
}

في الخلفية، تنشئ Kotlin تلقائيًا دوال جلب وتعيين للسمات التي حدّدتها في الفئة Aquarium، ما يتيح لك الوصول إلى السمات مباشرةً، مثل myAquarium.length.

الخطوة 3: إنشاء دالة main()

أنشئ ملفًا جديدًا باسم main.kt لتضمين الدالة main().

  1. في لوحة المشروع على يمين الشاشة، انقر بزر الماوس الأيمن على حزمة example.myapp.
  2. اختَر جديد > ملف / فئة Kotlin.
  3. في القائمة المنسدلة النوع، أبقِ الخيار ملف، وأطلِق على الملف الاسم main.kt. يتضمّن IntelliJ IDEA اسم الحزمة، ولكنّه لا يتضمّن تعريف فئة لملف.
  4. حدِّد الدالة buildAquarium() وأنشئ داخلها مثيلاً من Aquarium. لإنشاء مثيل، يمكنك الرجوع إلى الفئة كما لو كانت دالة، Aquarium(). يؤدي ذلك إلى استدعاء الدالة الإنشائية للفئة وإنشاء مثيل للفئة Aquarium، على غرار استخدام new في لغات أخرى.
  5. حدِّد الدالة main() واستدعِ الدالة buildAquarium().
package example.myapp

fun buildAquarium() {
    val myAquarium = Aquarium()
}

fun main() {
    buildAquarium()
}

الخطوة 4: إضافة طريقة

  1. في الفئة Aquarium، أضِف طريقة لطباعة خصائص أبعاد حوض السمك.
    fun printSize() {
        println("Width: $width cm " +
                "Length: $length cm " +
                "Height: $height cm ")
    }
  1. في main.kt، في buildAquarium()، استدعِ طريقة printSize() في myAquarium.
fun buildAquarium() {
    val myAquarium = Aquarium()
    myAquarium.printSize()
}
  1. نفِّذ برنامجك من خلال النقر على المثلث الأخضر بجانب الدالة main(). لاحظ النتيجة.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
  1. في buildAquarium()، أضِف رمزًا لضبط الارتفاع على 60 وطباعة خصائص الأبعاد المتغيرة.
fun buildAquarium() {
    val myAquarium = Aquarium()
    myAquarium.printSize()
    myAquarium.height = 60
    myAquarium.printSize()
}
  1. شغِّل برنامجك وراقِب الناتج.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
Width: 20 cm Length: 100 cm Height: 60 cm 

في هذه المهمة، ستنشئ دالة إنشاء للفئة، وستواصل العمل مع الخصائص.

الخطوة 1: إنشاء دالة إنشائية

في هذه الخطوة، ستضيف دالة إنشاء إلى الفئة Aquarium التي أنشأتها في المهمة الأولى. في المثال السابق، يتم إنشاء كل مثيل من Aquarium بالأبعاد نفسها. يمكنك تغيير الأبعاد بعد إنشائها من خلال ضبط الخصائص، ولكن سيكون من الأسهل إنشاءها بالحجم الصحيح منذ البداية.

في بعض لغات البرمجة، يتم تحديد الدالة الإنشائية من خلال إنشاء طريقة ضمن الفئة لها الاسم نفسه. في Kotlin، يمكنك تحديد الدالة الإنشائية مباشرةً في تعريف الفئة نفسه، وتحديد المَعلمات داخل أقواس كما لو كانت الفئة دالة. وكما هو الحال مع الدوال في Kotlin، يمكن أن تتضمّن هذه المَعلمات قيمًا تلقائية.

  1. في الفئة Aquarium التي أنشأتها سابقًا، غيِّر تعريف الفئة لتضمين ثلاث مَعلمات للدالة الإنشائية مع قيم تلقائية لكلّ من length وwidth وheight، ثمّ عيِّنها للسمات المقابلة.
class Aquarium(length: Int = 100, width: Int = 20, height: Int = 40) {
   // Dimensions in cm
   var length: Int = length
   var width: Int = width
   var height: Int = height
...
}
  1. تتمثّل طريقة Kotlin الأكثر اختصارًا في تحديد السمات مباشرةً باستخدام الدالة الإنشائية، وذلك باستخدام var أو val، كما تنشئ Kotlin دوال الجلب والتعديل تلقائيًا. بعد ذلك، يمكنك إزالة تعريفات السمة في نص الفئة.
class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40) {
...
}
  1. عند إنشاء عنصر Aquarium باستخدام الدالة الإنشائية، يمكنك عدم تحديد أي وسيط والحصول على القيم التلقائية، أو تحديد بعضها فقط، أو تحديد جميعها وإنشاء Aquarium بحجم مخصّص تمامًا. في الدالة buildAquarium()، جرِّب طرقًا مختلفة لإنشاء عنصر Aquarium باستخدام المَعلمات المُسمّاة.
fun buildAquarium() {
    val aquarium1 = Aquarium()
    aquarium1.printSize()
    // default height and length
    val aquarium2 = Aquarium(width = 25)
    aquarium2.printSize()
    // default width
    val aquarium3 = Aquarium(height = 35, length = 110)
    aquarium3.printSize()
    // everything custom
    val aquarium4 = Aquarium(width = 25, height = 35, length = 110)
    aquarium4.printSize()
}
  1. شغِّل البرنامج ولاحظ الناتج.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
Width: 25 cm Length: 100 cm Height: 40 cm 
Width: 20 cm Length: 110 cm Height: 35 cm 
Width: 25 cm Length: 110 cm Height: 35 cm 

لاحظ أنّه لم يكن عليك تحميل الدالة الإنشائية بشكل زائد وكتابة إصدار مختلف لكل حالة من هذه الحالات (بالإضافة إلى بعض الحالات الأخرى للتركيبات الأخرى). تنشئ لغة Kotlin ما هو مطلوب من القيم التلقائية والمَعلمات المسماة.

الخطوة 2: إضافة كتل init

تصرّح أدوات الإنشاء النموذجية أعلاه عن الخصائص وتعيّن قيمة تعبير لها. إذا كان المنشئ يحتاج إلى المزيد من رمز الإعداد الأوّلي، يمكن وضعه في واحد أو أكثر من كتل init. في هذه الخطوة، ستضيف بعض كتل init إلى فئة Aquarium.

  1. في الفئة Aquarium، أضِف كتلة init لطباعة أنّ العنصر يتم تهيئته، وكتلة ثانية لطباعة الحجم باللترات.
class Aquarium (var length: Int = 100, var width: Int = 20, var height: Int = 40) {
    init {
        println("aquarium initializing")
    }
    init {
        // 1 liter = 1000 cm^3
        println("Volume: ${width * length * height / 1000} l")
    }
}
  1. شغِّل البرنامج ولاحظ الناتج.
aquarium initializing
Volume: 80 l
Width: 20 cm Length: 100 cm Height: 40 cm 
aquarium initializing
Volume: 100 l
Width: 25 cm Length: 100 cm Height: 40 cm 
aquarium initializing
Volume: 77 l
Width: 20 cm Length: 110 cm Height: 35 cm 
aquarium initializing
Volume: 96 l
Width: 25 cm Length: 110 cm Height: 35 cm 

لاحظ أنّه يتم تنفيذ كتل init بالترتيب الذي تظهر به في تعريف الفئة، ويتم تنفيذها جميعًا عند استدعاء الدالة الإنشائية.

الخطوة 3: التعرّف على الدوال الإنشائية الثانوية

في هذه الخطوة، ستتعرّف على دوال الإنشاء الثانوية وتضيف واحدة إلى صفك. بالإضافة إلى الدالة الإنشائية الأساسية التي يمكن أن تحتوي على كتلة init واحدة أو أكثر، يمكن أن يحتوي صف Kotlin أيضًا على دالة إنشائية ثانوية واحدة أو أكثر للسماح بتعدّد تحميل الدالة الإنشائية، أي الدوال الإنشائية التي تحتوي على وسيطات مختلفة.

  1. في الفئة Aquarium، أضِف دالة إنشاء ثانوية تأخذ عددًا من الأسماك كمعلَمة، وذلك باستخدام الكلمة الرئيسية constructor. أنشئ سمة val لخزان الأسماك من أجل حساب حجم حوض السمك باللتر استنادًا إلى عدد الأسماك. افترض أنّ كل سمكة تحتاج إلى لترَين (2,000 سم^3) من الماء، بالإضافة إلى مساحة إضافية صغيرة حتى لا ينسكب الماء.
constructor(numberOfFish: Int) : this() {
    // 2,000 cm^3 per fish + extra room so water doesn't spill
    val tank = numberOfFish * 2000 * 1.1
}
  1. داخل الدالة الإنشائية الثانوية، احتفظ بالطول والعرض (اللذين تم ضبطهما في الدالة الإنشائية الأساسية) كما هما، واحتسِب الارتفاع المطلوب لجعل الخزان بالوحدة المحدّدة.
    // calculate the height needed
    height = (tank / (length * width)).toInt()
  1. في الدالة buildAquarium()، أضِف طلبًا لإنشاء Aquarium باستخدام الدالة الإنشائية الثانوية الجديدة. اطبع الحجم ومستوى الصوت.
fun buildAquarium() {
    val aquarium6 = Aquarium(numberOfFish = 29)
    aquarium6.printSize()
    println("Volume: ${aquarium6.width * aquarium6.length * aquarium6.height / 1000} l")
}
  1. شغِّل برنامجك وراقِب الناتج.
⇒ aquarium initializing
Volume: 80 l
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l

لاحظ أنّه تتم طباعة الحجم مرتين، مرة واحدة بواسطة الحظر init في الدالة الإنشائية الأساسية قبل تنفيذ الدالة الإنشائية الثانوية، ومرة واحدة بواسطة الرمز في buildAquarium().

كان بإمكانك أيضًا تضمين الكلمة الرئيسية constructor في الدالة الإنشائية الأساسية، ولكن ليس ذلك ضروريًا في معظم الحالات.

الخطوة 4: إضافة دالة جلب موقع إلكتروني جديد

في هذه الخطوة، يمكنك إضافة دالة جلب صريحة للسمة. تحدّد لغة Kotlin تلقائيًا دوال الجلب والضبط عند تحديد السمات، ولكن في بعض الأحيان يجب تعديل قيمة السمة أو احتسابها. على سبيل المثال، في الأعلى، طبعت حجم Aquarium. يمكنك إتاحة مستوى الصوت كسمة من خلال تحديد متغيّر ودالة getter له. بما أنّه يجب احتساب volume، يجب أن تعرض الدالة getter القيمة المحسوبة، ويمكنك إجراء ذلك باستخدام دالة من سطر واحد.

  1. في الفئة Aquarium، حدِّد السمة Int باسم volume، وحدِّد الطريقة get() التي تحسب الحجم في السطر التالي.
val volume: Int
    get() = width * height * length / 1000  // 1000 cm^3 = 1 l
  1. أزِلوا كتلة init التي تطبع مستوى الصوت.
  2. أزِل الرمز في buildAquarium() الذي يطبع مستوى الصوت.
  3. في طريقة printSize()، أضِف سطرًا لطباعة مستوى الصوت.
fun printSize() {
    println("Width: $width cm " +
            "Length: $length cm " +
            "Height: $height cm "
    )
    // 1 l = 1000 cm^3
    println("Volume: $volume l")
}
  1. شغِّل برنامجك وراقِب الناتج.
⇒ aquarium initializing
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l

الأبعاد والحجم هي نفسها كما كانت من قبل، ولكن لا تتم طباعة الحجم إلا مرة واحدة بعد أن يتم تهيئة الكائن بالكامل من خلال كل من الدالة الإنشائية الأساسية والدالة الإنشائية الثانوية.

الخطوة 5: إضافة أداة ضبط للسمة

في هذه الخطوة، يمكنك إنشاء أداة ضبط جديدة للسمة الخاصة بمستوى الصوت.

  1. في الفئة Aquarium، غيِّر volume إلى var حتى يمكن ضبطه أكثر من مرة.
  2. أضِف أداة ضبط للسمة volume من خلال إضافة الطريقة set() أسفل أداة الجلب، والتي تعيد احتساب الارتفاع استنادًا إلى كمية المياه المتوفّرة. حسب الاصطلاح، يكون اسم مَعلمة أداة الضبط هو value، ولكن يمكنك تغييره إذا كنت تفضّل ذلك.
var volume: Int
    get() = width * height * length / 1000
    set(value) {
        height = (value * 1000) / (width * length)
    }
  1. في buildAquarium()، أضِف رمزًا لضبط حجم حوض السمك على 70 لترًا. اطبع الحجم الجديد.
fun buildAquarium() {
    val aquarium6 = Aquarium(numberOfFish = 29)
    aquarium6.printSize()
    aquarium6.volume = 70
    aquarium6.printSize()
}
  1. نفِّذ برنامجك مرة أخرى ولاحظ التغييرات في الارتفاع ومستوى الصوت.
⇒ aquarium initialized
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l
Width: 20 cm Length: 100 cm Height: 35 cm 
Volume: 70 l

لم يتم استخدام أي معدّلات رؤية، مثل public أو private، في الرمز حتى الآن. ويرجع ذلك إلى أنّ كل شيء في Kotlin يكون متاحًا للجميع تلقائيًا، ما يعني أنّه يمكن الوصول إلى كل شيء في أي مكان، بما في ذلك الفئات والطرق والسمات ومتغيرات الأعضاء.

في Kotlin، يمكن أن تحتوي الفئات والكائنات والواجهات والدوال الإنشائية والدوال والخصائص وبرامج الضبط الخاصة بها على معدِّلات مستوى الوصول:

  • public تعني أنّه يمكن رؤية المحتوى خارج الصف. يكون كل شيء متاحًا للجميع تلقائيًا، بما في ذلك المتغيرات وطرق الفئة.
  • يعني internal أنّه لن يكون مرئيًا إلا داخل هذا القسم. الوحدة هي مجموعة من ملفات Kotlin يتم تجميعها معًا، مثل مكتبة أو تطبيق.
  • يعني private أنّه لن يكون مرئيًا إلا في هذا الصف (أو ملف المصدر إذا كنت تعمل باستخدام الدوال).
  • protected هي نفسها private، ولكن ستكون مرئية أيضًا لأي فئات فرعية.

يمكنك الاطّلاع على معدِّلات مستوى الظهور في مستندات Kotlin للحصول على مزيد من المعلومات.

متغيّرات الأعضاء

تكون الخصائص ضِمن فئة أو متغيرات الأعضاء public تلقائيًا. إذا حدّدتها باستخدام var، ستكون قابلة للتغيير، أي يمكن قراءتها وكتابتها. إذا حدّدتها باستخدام val، ستكون للقراءة فقط بعد التهيئة.

إذا كنت تريد خاصية يمكن لرمزك البرمجي قراءتها أو كتابتها، ولكن يمكن للرمز البرمجي الخارجي قراءتها فقط، يمكنك ترك الخاصية ودالة الجلب كعنصرَين عامَّين وتحديد دالة الضبط كعنصر خاص، كما هو موضّح أدناه.

var volume: Int
    get() = width * height * length / 1000
    private set(value) {
        height = (value * 1000) / (width * length)
    }

في هذه المهمة، ستتعرّف على طريقة عمل الفئات الفرعية والميراث في Kotlin. وهي تشبه ما رأيته بلغات أخرى، ولكن مع بعض الاختلافات.

في Kotlin، لا يمكن إنشاء فئات فرعية من الفئات تلقائيًا. وبالمثل، لا يمكن إلغاء خصائص ومتغيرات الأعضاء بواسطة الفئات الفرعية (على الرغم من إمكانية الوصول إليها).

يجب وضع علامة open على فئة للسماح بتصنيفها إلى فئات فرعية. وبالمثل، يجب وضع علامة open على السمات ومتغيّرات الأعضاء لتجاوزها في الفئة الفرعية. مطلوب استخدام الكلمة الرئيسية open لمنع تسريب تفاصيل التنفيذ عن طريق الخطأ كجزء من واجهة الفئة.

الخطوة 1: جعل صف Aquarium مفتوحًا

في هذه الخطوة، يمكنك إنشاء الفئة Aquariumopen، حتى تتمكّن من تجاهلها في الخطوة التالية.

  1. ضع الكلمة الرئيسية open بجانب الفئة Aquarium وجميع سماتها.
open class Aquarium (open var length: Int = 100, open var width: Int = 20, open var height: Int = 40) {
    open var volume: Int
        get() = width * height * length / 1000
        set(value) {
            height = (value * 1000) / (width * length)
        }
  1. أضِف السمة shape المفتوحة مع القيمة "rectangle".
   open val shape = "rectangle"
  1. أضِف السمة water المفتوحة مع دالة جلب تعرض% 90 من حجم Aquarium.
    open var water: Double = 0.0
        get() = volume * 0.9
  1. أضِف رمزًا إلى طريقة printSize() لطباعة الشكل وكمية المياه كنسبة مئوية من الحجم.
fun printSize() {
    println(shape)
    println("Width: $width cm " +
            "Length: $length cm " +
            "Height: $height cm ")
    // 1 l = 1000 cm^3
    println("Volume: $volume l Water: $water l (${water/volume*100.0}% full)")
}
  1. في buildAquarium()، غيِّر الرمز لإنشاء Aquarium باستخدام width = 25 وlength = 25 وheight = 40.
fun buildAquarium() {
    val aquarium6 = Aquarium(length = 25, width = 25, height = 40)
    aquarium6.printSize()
}
  1. نفِّذ برنامجك ولاحظ الناتج الجديد.
⇒ aquarium initializing
rectangle
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 25 l Water: 22.5 l (90.0% full)

الخطوة 2: إنشاء فئة فرعية

  1. أنشئ فئة فرعية من Aquarium باسم TowerTank، والتي تنفّذ خزانًا أسطوانيًا مستديرًا بدلاً من خزان مستطيل. يمكنك إضافة TowerTank أسفل Aquarium، لأنّه يمكنك إضافة صف آخر في الملف نفسه الذي يتضمّن الصف Aquarium.
  2. في TowerTank، يمكنك تجاهل السمة height التي تم تحديدها في الدالة الإنشائية. لإلغاء إحدى السمات، استخدِم الكلمة الرئيسية override في الفئة الفرعية.
  1. اجعل الدالة الإنشائية الخاصة بـ TowerTank تأخذ diameter. استخدِم diameter لكل من length وwidth عند استدعاء الدالة الإنشائية في الفئة الرئيسية Aquarium.
class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
  1. تجاوز السمة "الحجم" لاحتساب أسطوانة. صيغة الأسطوانة هي "باي" مضروبًا في مربع نصف القطر مضروبًا في الارتفاع. عليك استيراد الثابت PI من java.lang.Math.
    override var volume: Int
    // ellipse area = π * r1 * r2
    get() = (width/2 * length/2 * height / 1000 * PI).toInt()
    set(value) {
        height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
    }
  1. في TowerTank، استبدِل السمة water بنسبة% 80 من مستوى الصوت.
override var water = volume * 0.8
  1. تجاهل قيمة shape واستخدام "cylinder"
override val shape = "cylinder"
  1. يجب أن تبدو فئة TowerTank النهائية على النحو التالي.

Aquarium.kt:

package example.myapp

import java.lang.Math.PI

... // existing Aquarium class

class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
    override var volume: Int
    // ellipse area = π * r1 * r2
    get() = (width/2 * length/2 * height / 1000 * PI).toInt()
    set(value) {
        height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
    }

    override var water = volume * 0.8
    override val shape = "cylinder"
}
  1. في buildAquarium()، أنشئ TowerTank بقطر 25 سم وارتفاع 45 سم. اطبع المقاس.

main.kt:

package example.myapp

fun buildAquarium() {
    val myAquarium = Aquarium(width = 25, length = 25, height = 40)
    myAquarium.printSize()
    val myTower = TowerTank(diameter = 25, height = 40)
    myTower.printSize()
}
  1. شغِّل برنامجك وراقِب الناتج.
⇒ aquarium initializing
rectangle
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 25 l Water: 22.5 l (90.0% full)
aquarium initializing
cylinder
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 18 l Water: 14.4 l (80.0% full)

في بعض الأحيان، تريد تحديد سلوك أو خصائص مشتركة ليتم مشاركتها بين بعض الفئات ذات الصلة. توفّر لغة Kotlin طريقتَين لإجراء ذلك، وهما الواجهات والفئات المجردة. في هذه المهمة، ستنشئ فئة AquariumFish مجرّدة للخصائص المشتركة بين جميع الأسماك. يمكنك إنشاء واجهة باسم FishAction لتحديد السلوك المشترك بين جميع الأسماك.

  • لا يمكن إنشاء مثيل لفئة مجرّدة أو واجهة بمفردهما، ما يعني أنّه لا يمكنك إنشاء كائنات من هذين النوعين مباشرةً.
  • تحتوي الفئات المجردة على دوال إنشائية.
  • لا يمكن أن تحتوي الواجهات على أي منطق إنشاء أو تخزين أي حالة.

الخطوة 1: إنشاء فئة مجرّدة

  1. ضمن example.myapp، أنشئ ملفًا جديدًا باسم AquariumFish.kt.
  2. أنشئ فئة، تُعرف أيضًا باسم AquariumFish، وضع علامة abstract عليها.
  3. أضِف سمة String واحدة، وهي color، وضع علامة abstract عليها.
package example.myapp

abstract class AquariumFish {
    abstract val color: String
}
  1. أنشئ فئتَين فرعيتَين من AquariumFish وShark وPlecostomus.
  2. بما أنّ color مجرّد، يجب أن تنفّذه الفئات الفرعية. اجعل Shark باللون الرمادي وPlecostomus باللون الذهبي.
class Shark: AquariumFish() {
    override val color = "gray"
}

class Plecostomus: AquariumFish() {
    override val color = "gold"
}
  1. في ملف main.kt، أنشئ الدالة makeFish() لاختبار صفوفك. أنشئ مثيلاً من Shark وPlecostomus، ثم اطبع لون كل منهما.
  2. احذف رمز الاختبار السابق في main() وأضِف طلبًا إلى makeFish(). يجب أن يبدو الرمز البرمجي على النحو الموضّح أدناه.

main.kt:

package example.myapp

fun makeFish() {
    val shark = Shark()
    val pleco = Plecostomus()

    println("Shark: ${shark.color}")
    println("Plecostomus: ${pleco.color}")
}

fun main () {
    makeFish()
}
  1. شغِّل برنامجك وراقِب الناتج.
⇒ Shark: gray 
Plecostomus: gold

يمثّل الرسم البياني التالي الفئة Shark والفئة Plecostomus، وهما فئتان فرعيتان من الفئة المجردة AquariumFish.

مخطّط بياني يعرض الفئة المجردة AquariumFish وفئتين فرعيتين هما Shark وPlecostumus

الخطوة 2: إنشاء واجهة

  1. في ملف AquariumFish.kt، أنشئ واجهة باسم FishAction تتضمّن طريقة eat().
interface FishAction  {
    fun eat()
}
  1. أضِف FishAction إلى كل فئة فرعية، ونفِّذ eat() من خلال جعلها تطبع ما تفعله السمكة.
class Shark: AquariumFish(), FishAction {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}

class Plecostomus: AquariumFish(), FishAction {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}
  1. في الدالة makeFish()، اجعل كل سمكة أنشأتها تأكل شيئًا ما عن طريق استدعاء eat().
fun makeFish() {
    val shark = Shark()
    val pleco = Plecostomus()
    println("Shark: ${shark.color}")
    shark.eat()
    println("Plecostomus: ${pleco.color}")
    pleco.eat()
}
  1. شغِّل برنامجك وراقِب الناتج.
⇒ Shark: gray
hunt and eat fish
Plecostomus: gold
eat algae

يمثّل الرسم البياني التالي الفئة Shark والفئة Plecostomus، وكلتاهما تتألفان من الواجهة FishAction وتنفّذانها.

حالات استخدام الفئات المجردة مقارنةً بالواجهات

الأمثلة أعلاه بسيطة، ولكن عندما يكون لديك الكثير من الفئات المترابطة، يمكن أن تساعدك الفئات المجردة والواجهات في الحفاظ على تصميمك أكثر نظافة وتنظيمًا وسهولة في الصيانة.

كما ذكرنا أعلاه، يمكن أن تحتوي الفئات المجردة على دوال إنشاء، بينما لا يمكن أن تحتوي الواجهات على دوال إنشاء، ولكنّها تتشابه إلى حد كبير في ما عدا ذلك. إذًا، متى يجب استخدام كل منهما؟

عند استخدام الواجهات لإنشاء فئة، يتم توسيع وظائف الفئة من خلال مثيلات الفئة التي تحتوي عليها. تميل عملية الإنشاء إلى تسهيل إعادة استخدام الرمز البرمجي وفهمه مقارنةً بعملية الوراثة من فئة مجرّدة. يمكنك أيضًا استخدام واجهات متعدّدة في فئة، ولكن يمكنك فقط إنشاء فئة فرعية من فئة مجرّدة واحدة.

غالبًا ما تؤدي عملية الإنشاء إلى تغليف أفضل وربط أقل (اعتماد متبادل) وواجهات أكثر وضوحًا ورموز برمجية أكثر قابلية للاستخدام. لهذه الأسباب، يُفضّل استخدام التركيب مع الواجهات. من ناحية أخرى، يميل التوريث من فئة مجرّدة إلى أن يكون مناسبًا بشكل طبيعي لبعض المشاكل. لذلك، يجب أن تفضّل استخدام التركيب، ولكن عندما يكون الوراثة منطقيًا، يتيح لك Kotlin استخدامه أيضًا.

  • استخدِم واجهة إذا كان لديك الكثير من الطرق وتنفيذ تلقائي واحد أو اثنان، مثلاً كما في AquariumAction أدناه.
interface AquariumAction {
    fun eat()
    fun jump()
    fun clean()
    fun catchFish()
    fun swim()  {
        println("swim")
    }
}
  • استخدِم فئة مجرّدة في أي وقت لا يمكنك فيه إكمال فئة. على سبيل المثال، بالرجوع إلى الفئة AquariumFish، يمكنك جعل جميع AquariumFish تنفّذ FishAction، وتوفير تنفيذ تلقائي لـ eat مع ترك color مجردة، لأنّه لا يوجد لون تلقائي للسمك.
interface FishAction  {
    fun eat()
}

abstract class AquariumFish: FishAction {
   abstract val color: String
   override fun eat() = println("yum")
}

قدّمت المهمة السابقة الفئات المجردة والواجهات وفكرة التركيب. تفويض الواجهة هو أسلوب متقدّم يتم فيه تنفيذ طرق الواجهة بواسطة عنصر مساعد (أو مفوَّض)، ثم يتم استخدام هذا العنصر بواسطة فئة. يمكن أن يكون هذا الأسلوب مفيدًا عند استخدام واجهة في سلسلة من الفئات غير المرتبطة: يمكنك إضافة وظائف الواجهة المطلوبة إلى فئة مساعدة منفصلة، وتستخدم كل فئة مثيلاً لفئة المساعدة لتنفيذ الوظائف.

في هذه المهمة، ستستخدم تفويض الواجهة لإضافة وظائف إلى فئة.

الخطوة 1: إنشاء واجهة جديدة

  1. في ملف AquariumFish.kt، أزِل الفئة AquariumFish. بدلاً من الوراثة من الفئة AquariumFish، ستنفّذ الفئتان Plecostomus وShark واجهات لكل من إجراء السمكة ولونها.
  2. أنشئ واجهة جديدة، FishColor، تحدّد اللون كسلسلة.
interface FishColor {
    val color: String
}
  1. غيِّر Plecostomus لتنفيذ واجهتَين، FishAction، وFishColor. عليك تجاهل color من FishColor وeat() من FishAction.
class Plecostomus: FishAction, FishColor {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}
  1. غيِّر فئة Shark لتنفيذ الواجهتين FishAction وFishColor أيضًا بدلاً من الوراثة من AquariumFish.
class Shark: FishAction, FishColor {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}
  1. يجب أن تبدو التعليمات البرمجية المكتملة على النحو التالي:
package example.myapp

interface FishAction {
    fun eat()
}

interface FishColor {
    val color: String
}

class Plecostomus: FishAction, FishColor {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}

class Shark: FishAction, FishColor {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}

الخطوة 2: إنشاء فئة ذات مثيل واحد

بعد ذلك، يمكنك تنفيذ عملية الإعداد الخاصة بجزء التفويض من خلال إنشاء فئة مساعدة تنفّذ FishColor. يمكنك إنشاء فئة أساسية باسم GoldColor تنفّذ FishColor، وكل ما تفعله هو تحديد أنّ لونها ذهبي.

ليس من المنطقي إنشاء مثيلات متعددة من GoldColor، لأنّها ستؤدي جميعًا الوظيفة نفسها بالضبط. لذا، تتيح لك لغة Kotlin تعريف فئة يمكنك إنشاء مثيل واحد منها فقط باستخدام الكلمة الرئيسية object بدلاً من class. ستنشئ لغة Kotlin مثيلاً واحدًا، ويتم الرجوع إلى هذا المثيل من خلال اسم الفئة. بعد ذلك، يمكن لجميع العناصر الأخرى استخدام هذا المثال فقط، ولا يمكن إنشاء أمثلة أخرى من هذه الفئة. إذا كنت على دراية بنمط Singleton، إليك كيفية تنفيذ هذا النمط في Kotlin.

  1. في ملف AquariumFish.kt، أنشئ عنصرًا لـ GoldColor. تجاوز اللون
object GoldColor : FishColor {
   override val color = "gold"
}

الخطوة 3: إضافة تفويض الواجهة إلى FishColor

أنت الآن جاهز لاستخدام تفويض الواجهة.

  1. في ملف AquariumFish.kt، أزِل عملية الإلغاء لـ color من Plecostomus.
  2. غيِّر الفئة Plecostomus للحصول على لونها من GoldColor. يمكنك إجراء ذلك عن طريق إضافة by GoldColor إلى تعريف الفئة، ما يؤدي إلى إنشاء التفويض. هذا يعني أنّه بدلاً من تنفيذ FishColor، عليك استخدام عملية التنفيذ التي يوفّرها GoldColor. لذا، في كل مرة يتم فيها الوصول إلى color، يتم تفويض GoldColor.
class Plecostomus:  FishAction, FishColor by GoldColor {
   override fun eat() {
       println("eat algae")
   }
}

في الصف الحالي، سيكون لون جميع أسماك Plecos ذهبيًا، ولكن في الواقع، تتوفّر هذه الأسماك بألوان عديدة. يمكنك حلّ هذه المشكلة عن طريق إضافة مَعلمة دالة إنشائية للّون مع GoldColor كاللّون التلقائي لـ Plecostomus.

  1. غيِّر فئة Plecostomus لتأخذ قيمة تم تمريرها في fishColor باستخدام الدالة الإنشائية، واضبط القيمة التلقائية على GoldColor. غيِّر التفويض من by GoldColor إلى by fishColor.
class Plecostomus(fishColor: FishColor = GoldColor):  FishAction,
       FishColor by fishColor {
   override fun eat() {
       println("eat algae")
   }
}

الخطوة 4: إضافة تفويض الواجهة إلى FishAction

وبالطريقة نفسها، يمكنك استخدام تفويض الواجهة لـ FishAction.

  1. في ملف AquariumFish.kt، أنشئ فئة PrintingFishAction تنفّذ الواجهة FishAction، وتأخذ String وfood، ثم تطبع ما تأكله السمكة.
class PrintingFishAction(val food: String) : FishAction {
    override fun eat() {
        println(food)
    }
}
  1. في الفئة Plecostomus، أزِل وظيفة التجاوز eat()، لأنّك ستستبدلها بوظيفة تفويض.
  2. في تعريف Plecostomus، فوِّض FishAction إلى PrintingFishAction، مع تمرير "eat algae".
  3. مع كل هذا التفويض، لا يوجد رمز في نص الفئة Plecostomus، لذا أزِل {}، لأنّ جميع عمليات الإلغاء تتم معالجتها من خلال تفويض الواجهة.
class Plecostomus (fishColor: FishColor = GoldColor):
        FishAction by PrintingFishAction("eat algae"),
        FishColor by fishColor

يمثّل الرسم البياني التالي الفئتين Shark وPlecostomus، وكلتاهما تتألفان من الواجهتين PrintingFishAction وFishColor، ولكن يتم تفويض التنفيذ إليهما.

تعدّ تفويض الواجهة أداة فعّالة، وعليك بشكل عام التفكير في كيفية استخدامها كلما احتجت إلى استخدام فئة مجرّدة بلغة أخرى. تتيح لك استخدام التركيب لتضمين السلوكيات، بدلاً من الحاجة إلى العديد من الفئات الفرعية، كل منها متخصص بطريقة مختلفة.

تشبه فئة البيانات struct في بعض اللغات الأخرى، فهي موجودة بشكل أساسي للاحتفاظ ببعض البيانات، ولكنّ عنصر فئة البيانات يظلّ عنصرًا. تتضمّن عناصر فئة بيانات Kotlin بعض المزايا الإضافية، مثل الأدوات المساعدة للطباعة والنسخ. في هذه المهمة، ستنشئ فئة بيانات بسيطة وتتعرّف على الميزات التي توفّرها Kotlin لفئات البيانات.

الخطوة 1: إنشاء فئة بيانات

  1. أضِف حزمة جديدة decor ضمن الحزمة example.myapp لتضمين الرمز الجديد. انقر بزر الماوس الأيمن على example.myapp في نافذة المشروع واختَر ملف > جديد > حزمة.
  2. في الحزمة، أنشئ فئة جديدة باسم Decoration.
package example.myapp.decor

class Decoration {
}
  1. لتحويل Decoration إلى فئة بيانات، أضِف البادئة data إلى تعريف الفئة.
  2. أضِف السمة String التي تحمل الاسم rocks لمنح الفئة بعض البيانات.
data class Decoration(val rocks: String) {
}
  1. في الملف، خارج الفئة، أضِف الدالة makeDecorations() لإنشاء وطباعة مثيل من Decoration باستخدام "granite".
fun makeDecorations() {
    val decoration1 = Decoration("granite")
    println(decoration1)
}
  1. أضِف الدالة main() لاستدعاء makeDecorations()، ثم شغِّل برنامجك. لاحظ الناتج المعقول الذي تم إنشاؤه لأنّ هذا النوع هو فئة بيانات.
⇒ Decoration(rocks=granite)
  1. في makeDecorations()، أنشئ عنصرَين آخرَين من النوع Decoration يكون كلاهما "slate" واطبعهما.
fun makeDecorations() {
    val decoration1 = Decoration("granite")
    println(decoration1)

    val decoration2 = Decoration("slate")
    println(decoration2)

    val decoration3 = Decoration("slate")
    println(decoration3)
}
  1. في makeDecorations()، أضِف عبارة طباعة تعرض نتيجة مقارنة decoration1 بـ decoration2، وعبارة ثانية تقارن decoration3 بـ decoration2. استخدِم طريقة equals()‎ التي توفّرها فئات البيانات.
    println (decoration1.equals(decoration2))
    println (decoration3.equals(decoration2))
  1. تشغيل الرمز
⇒ Decoration(rocks=granite)
Decoration(rocks=slate)
Decoration(rocks=slate)
false
true

الخطوة 2: استخدام تفكيك البنية

للوصول إلى خصائص عنصر بيانات وتعيينها إلى متغيرات، يمكنك تعيينها واحدة تلو الأخرى، على النحو التالي.

val rock = decoration.rock
val wood = decoration.wood
val diver = decoration.diver

بدلاً من ذلك، يمكنك إنشاء متغيرات، واحد لكل موقع، وتعيين عنصر البيانات لمجموعة المتغيرات. تضع لغة Kotlin قيمة الخاصية في كل متغيّر.

val (rock, wood, diver) = decoration

يُطلق على هذه العملية اسم التفكيك وهي اختصار مفيد. يجب أن يتطابق عدد المتغيرات مع عدد السمات، ويتم تعيين المتغيرات بالترتيب الذي تم الإعلان عنها به في الفئة. في ما يلي مثال كامل يمكنك تجربته في Decoration.kt.

// Here is a data class with 3 properties.
data class Decoration2(val rocks: String, val wood: String, val diver: String){
}

fun makeDecorations() {
    val d5 = Decoration2("crystal", "wood", "diver")
    println(d5)

// Assign all properties to variables.
    val (rock, wood, diver) = d5
    println(rock)
    println(wood)
    println(diver)
}
⇒ Decoration2(rocks=crystal, wood=wood, diver=diver)
crystal
wood
diver

إذا لم تكن بحاجة إلى واحدة أو أكثر من الخصائص، يمكنك تخطّيها باستخدام _ بدلاً من اسم متغيّر، كما هو موضّح في الرمز البرمجي أدناه.

    val (rock, _, diver) = d5

في هذه المهمة، ستتعرّف على بعض الفئات ذات الأغراض الخاصة في Kotlin، بما في ذلك ما يلي:

  • فئات Singleton
  • عمليات التعداد
  • الفئات المحكمة

الخطوة 1: تذكُّر فئات العناصر الفردية

تذكَّر المثال السابق الذي يتضمّن الفئة GoldColor.

object GoldColor : FishColor {
   override val color = "gold"
}

بما أنّ كل مثيل من GoldColor ينفّذ الإجراء نفسه، يتم تعريفه على أنّه object بدلاً من class لجعله كائنًا فرديًا. يمكن أن يكون هناك مثيل واحد فقط.

الخطوة 2: إنشاء تعداد

تتيح لك لغة Kotlin أيضًا استخدام التعدادات التي تسمح لك بتعداد عنصر ما والإشارة إليه بالاسم، تمامًا كما هو الحال في اللغات الأخرى. يمكنك تعريف تعداد باستخدام الكلمة الرئيسية enum قبل التعريف. لا يحتاج تعريف التعداد الأساسي إلا إلى قائمة بالأسماء، ولكن يمكنك أيضًا تحديد حقل واحد أو أكثر مرتبط بكل اسم.

  1. في ملف Decoration.kt، جرِّب مثالاً على تعداد.
enum class Color(val rgb: Int) {
   RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF);
}

تشبه التعدادات إلى حد ما الكائنات الفردية، إذ لا يمكن أن يكون هناك سوى واحد، وواحد فقط من كل قيمة في التعداد. على سبيل المثال، يمكن أن يكون هناك Color.RED واحد فقط وColor.GREEN واحد وColor.BLUE واحد. في هذا المثال، يتم تعيين قيم الأحمر والأخضر والأزرق إلى السمة rgb لتمثيل مكوّنات اللون. يمكنك أيضًا الحصول على القيمة الترتيبية لتعداد باستخدام السمة ordinal، واسمه باستخدام السمة name.

  1. جرِّب مثالاً آخر على تعداد.
enum class Direction(val degrees: Int) {
    NORTH(0), SOUTH(180), EAST(90), WEST(270)
}

fun main() {
    println(Direction.EAST.name)
    println(Direction.EAST.ordinal)
    println(Direction.EAST.degrees)
}
⇒ EAST
2
90

الخطوة 3: إنشاء فئة محكمة الإغلاق

الفئة المحكمة هي فئة يمكن إنشاء فئات فرعية منها، ولكن فقط داخل الملف الذي تم تعريفها فيه. إذا حاولت إنشاء فئة فرعية من الفئة في ملف مختلف، سيظهر لك خطأ.

بما أنّ الفئات والفئات الفرعية موجودة في الملف نفسه، ستعرف لغة Kotlin جميع الفئات الفرعية بشكل ثابت. وهذا يعني أنّه في وقت الترجمة البرمجية، يرى المترجم البرمجي جميع الفئات والفئات الفرعية ويعرف أنّ هذه هي جميعها، لذا يمكن للمترجم البرمجي إجراء عمليات تحقّق إضافية نيابةً عنك.

  1. في ملف AquariumFish.kt، جرِّب مثالاً على فئة محكمة، مع الحفاظ على الطابع المائي.
sealed class Seal
class SeaLion : Seal()
class Walrus : Seal()

fun matchSeal(seal: Seal): String {
   return when(seal) {
       is Walrus -> "walrus"
       is SeaLion -> "sea lion"
   }
}

لا يمكن إنشاء فئة فرعية من الفئة Seal في ملف آخر. إذا أردت إضافة المزيد من أنواع Seal، عليك إضافتها في الملف نفسه. وهذا يجعل الفئات المحكمة طريقة آمنة لتمثيل عدد ثابت من الأنواع. على سبيل المثال، تكون الفئات المحكمة مفيدة لعرض النجاح أو الخطأ من واجهة برمجة تطبيقات الشبكة.

لقد تناولنا في هذا الدرس الكثير من المعلومات. وعلى الرغم من أنّ معظمها يجب أن يكون مألوفًا من لغات البرمجة الأخرى التي تعتمد على العناصر، تضيف Kotlin بعض الميزات للحفاظ على الرمز البرمجي موجزًا وسهل القراءة.

الفئات والدوال الإنشائية

  • حدِّد فئة في Kotlin باستخدام class.
  • تنشئ Kotlin تلقائيًا دوال setter وgetter للخصائص.
  • يمكنك تحديد الدالة الإنشائية الأساسية مباشرةً في تعريف الفئة. على سبيل المثال:
    class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40)
  • إذا كان المنشئ الأساسي يحتاج إلى رمز إضافي، فاكتبه في كتلة واحدة أو أكثر من كتل init.
  • يمكن للفئة تحديد منشئ ثانوي واحد أو أكثر باستخدام constructor، ولكن أسلوب Kotlin هو استخدام دالة مصنع بدلاً من ذلك.

معدّلات الوصول إلى البيانات والفئات الفرعية

  • تكون جميع الفئات والدوال في Kotlin public تلقائيًا، ولكن يمكنك استخدام المعدِّلات لتغيير مستوى العرض إلى internal أو private أو protected.
  • لإنشاء فئة فرعية، يجب وضع العلامة open على الفئة الرئيسية.
  • لتجاوز الطرق والسمات في فئة فرعية، يجب وضع علامة open على الطرق والسمات في الفئة الرئيسية.
  • لا يمكن إنشاء فئة فرعية من فئة محكمة الإغلاق إلا في الملف نفسه الذي تم تعريفها فيه. يمكنك إنشاء فئة محكمة الإغلاق عن طريق إضافة البادئة sealed إلى تعريفها.

فئات البيانات والعناصر الفردية وقيم التعداد

  • أنشئ فئة بيانات عن طريق إضافة البادئة data إلى تعريفها.
  • التفكيك هو اختصار لتعيين خصائص data كائن إلى متغيرات منفصلة.
  • يمكنك إنشاء فئة ذات مثيل واحد باستخدام object بدلاً من class.
  • حدِّد تعدادًا باستخدام enum class.

الفئات المجردة والواجهات والتفويض

  • تُعد الفئات المجردة والواجهات طريقتَين لمشاركة السلوك المشترك بين الفئات.
  • يحدّد الصنف التجريدي الخصائص والسلوك، ولكنّه يترك التنفيذ للأصناف الفرعية.
  • تحدّد الواجهة السلوك، وقد توفّر عمليات تنفيذ تلقائية لبعض السلوك أو كله.
  • عند استخدام الواجهات لإنشاء فئة، يتم توسيع وظائف الفئة من خلال مثيلات الفئة التي تحتوي عليها.
  • يستخدم تفويض الواجهة التركيب، ولكنّه يفوّض أيضًا التنفيذ إلى فئات الواجهة.
  • التركيب هو طريقة فعّالة لإضافة وظائف إلى فئة باستخدام تفويض الواجهة. بشكل عام، يُفضّل استخدام التركيب، ولكنّ الوراثة من فئة مجرّدة هي الأنسب لبعض المشاكل.

مستندات Kotlin

إذا أردت الحصول على مزيد من المعلومات حول أي موضوع في هذه الدورة التدريبية، أو إذا واجهتك أي مشكلة، يمكنك الانتقال إلى https://kotlinlang.org.

برامج تعليمية حول Kotlin

يتضمّن الموقع الإلكتروني https://try.kotlinlang.org برامج تعليمية غنية بصريًا تُعرف باسم Kotlin Koans، ومترجم مستند إلى الويب، ومجموعة كاملة من المستندات المرجعية مع أمثلة.

دورة Udacity التدريبية

للاطّلاع على دورة Udacity التدريبية حول هذا الموضوع، يُرجى الانتقال إلى برنامج Kotlin التدريبي للمبرمجين.

IntelliJ IDEA

يمكنك العثور على مستندات IntelliJ IDEA على موقع JetBrains الإلكتروني.

يسرد هذا القسم مهامًا منزلية محتملة للطلاب الذين يعملون على هذا الدرس التطبيقي العملي كجزء من دورة تدريبية يقودها مدرّب. على المعلّم تنفيذ ما يلي:

  • حدِّد واجبًا منزليًا إذا لزم الأمر.
  • توضيح كيفية إرسال الواجبات المنزلية للطلاب
  • وضع درجات للواجبات المنزلية

يمكن للمدرّبين استخدام هذه الاقتراحات بالقدر الذي يريدونه، ويجب ألا يترددوا في تكليف الطلاب بأي واجبات منزلية أخرى يرونها مناسبة.

إذا كنت تعمل على هذا الدرس العملي بنفسك، يمكنك استخدام مهام الواجب المنزلي هذه لاختبار معلوماتك.

الإجابة عن هذه الأسئلة

السؤال 1

تحتوي الفئات على طريقة خاصة تعمل كمخطط لإنشاء عناصر من تلك الفئة. ما اسم الطريقة؟

▢ عامل بناء

‫▢ أداة إنشاء

▢ طريقة وضع التصميم

▢ مخطط

السؤال 2

أي من العبارات التالية غير صحيحة بشأن الواجهات والفئات المجردة؟

▢ يمكن أن تحتوي الفئات المجردة على دوال إنشاء.

▢ لا يمكن أن تحتوي الواجهات على دوال إنشائية.

▢ يمكن إنشاء مثيل للواجهات والفئات المجردة مباشرةً.

▢ يجب أن يتم تنفيذ الخصائص المجردة بواسطة الفئات الفرعية للفئة المجردة.

السؤال 3

أيّ مما يلي ليس معدِّلاً لمدى الوصول في Kotlin للسمات والطرق وما إلى ذلك؟

internal

nosubclass

protected

private

السؤال 4

لنفترض فئة البيانات التالية:
data class Fish(val name: String, val species:String, val colors:String)
أيّ مما يلي ليس رمزًا صالحًا لإنشاء كائن Fish وتفكيكه؟

val (name1, species1, colors1) = Fish("Pat", "Plecostomus", "gold")

val (name2, _, colors2) = Fish("Bitey", "shark", "gray")

val (name3, species3, _) = Fish("Amy", "angelfish", "blue and black stripes")

val (name4, species4, colors4) = Fish("Harry", "halibut")

السؤال 5

لنفترض أنّك تمتلك حديقة حيوانات تضم الكثير من الحيوانات التي تحتاج إلى رعاية. أيّ مما يلي لن يكون جزءًا من تنفيذ الرعاية؟

interface لأنواع الأطعمة المختلفة التي تتناولها الحيوانات

▢ فئة abstract Caretaker يمكنك من خلالها إنشاء أنواع مختلفة من مقدّمي الرعاية

interface لمنح حيوان مياه نظيفة

‫▢ A data class for an entry in a feeding schedule

الانتقال إلى الدرس التالي: 5.1 الإضافات

للحصول على نظرة عامة على الدورة التدريبية، بما في ذلك روابط تؤدي إلى دروس تطبيقية أخرى حول الترميز، يُرجى الاطّلاع على "برنامج Kotlin التدريبي للمبرمجين: مرحبًا بك في الدورة التدريبية".