الإصدار التجريبي من Kotlin للمبرمجين 5.1: الإضافات

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

مقدمة

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

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

ما يجب معرفته

  • بنية دوال Kotlin وفئاتها وأساليبها
  • كيفية استخدام Kotlin's REPL (Read-Eval-Print Loop) في IntelliJ IDEA
  • كيفية إنشاء صف جديد في IntelliJ IDEA وإدارة برنامج

ما ستتعرَّف عليه

  • كيفية العمل مع الأزواج والثلاثيات
  • مزيد من المعلومات عن المجموعات
  • تعريف الثوابت واستخدامها
  • دوال إضافات الكتابة

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

  • مزيد من المعلومات عن الأزواج والثلاثيات وعلامات التجزئة في REPL
  • تعلّم طرقًا مختلفة لتنظيم الثوابت
  • كتابة دالة الإضافة وسمة الإضافة

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

لنفترض أن لديك List من الأسماك، ووظيفة isFreshWater() للتحقق مما إذا كانت الأسماك من الماء العذب أو سمك المياه المالحة. تعرض List.partition() قائمتين، إحداهما تتضمن العناصر التي يكون فيها الشرط true، والأخرى للسلع التي يكون الشرط فيها false.

val twoLists = fish.partition { isFreshWater(it) }
println("freshwater: ${twoLists.first}")
println("saltwater: ${twoLists.second}")

الخطوة 1: إنشاء بعض الأزواج والثلاثيات

  1. افتح REPL (الأدوات &gt؛ Kotlin &gt؛ Kotlin REPL).
  2. أنشئ زوجًا من الأجهزة، وربط جهازًا بالأغراض التي تُستخدم لأجلها، ثم اطبع القيم. يمكنك إنشاء زوج من خلال إنشاء تعبير يربط قيمتين، مثل سلسلتين، بالكلمة الرئيسية to، ثم استخدام .first أو .second للإشارة إلى كل قيمة.
val equipment = "fish net" to "catching fish"
println("${equipment.first} used for ${equipment.second}")
⇒ fish net used for catching fish
  1. يمكنك إنشاء ثلاثي أو طباعته باستخدام toString()، ثم تحويله إلى قائمة باستخدام toList(). يمكنك إنشاء ثلاث شرائح باستخدام Triple() مع 3 قيم. استخدِم .first و.second و.third للإشارة إلى كل قيمة.
val numbers = Triple(6, 9, 42)
println(numbers.toString())
println(numbers.toList())
⇒ (6, 9, 42)
[6, 9, 42]

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

  1. أنشِئ زوجًا حيث يكون الجزء الأول منه هو نفسه.
val equipment2 = ("fish net" to "catching fish") to "equipment"
println("${equipment2.first} is ${equipment2.second}\n")
println("${equipment2.first.second}")
⇒ (fish net, catching fish) is equipment
⇒ catching fish

الخطوة 2: تدمير بعض الأزواج والثلاثيات

يُسمى فصل الأزواج والثلاثيات في أجزائها التدمير. خصِّص الزوج أو الثلاثة قيم إلى العدد المناسب من المتغيّرات، وستعمل لغة Kotlin على تخصيص قيمة كل جزء بالترتيب.

  1. عليك بعد ذلك إزالة الإقران وطباعة القيم.
val equipment = "fish net" to "catching fish"
val (tool, use) = equipment
println("$tool is used for $use")
⇒ fish net is used for catching fish
  1. حوِّل ثلاث مرات واحصل على القيم المطلوبة.
val numbers = Triple(6, 9, 42)
val (n1, n2, n3) = numbers
println("$n1 $n2 $n3")
⇒ 6 9 42

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

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

الخطوة 1: مزيد من المعلومات عن القوائم

  1. تم تقديم القوائم والقوائم القابلة للتغيير في درس سابق. وهي عبارة عن بنية بيانات مفيدة للغاية، لذلك يوفر Kotlin عددًا من الوظائف المدمجة للقوائم. راجع هذه القائمة الجزئية من الدوال للقوائم. يمكنك العثور على بيانات كاملة في وثائق Kotlin لـ List وMutableList.

الوظيفة

الغرض

add(element: E)

أضِف عنصرًا إلى قائمة التغييرات.

remove(element: E)

إزالة عنصر من قائمة قابلة للتغيير.

reversed()

عرض نسخة من القائمة بترتيب العناصر العكسية.

contains(element: E)

اعرض true إذا كانت القائمة تحتوي على العنصر.

subList(fromIndex: Int, toIndex: Int)

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

  1. لا يزال العمل في REPL يؤدي إلى إنشاء قائمة بالأرقام والاتصال بالرقم sum() فيها. تلخّص هذه المقالة كل العناصر.
val list = listOf(1, 5, 3, 4)
println(list.sum())
⇒ 13
  1. أنشئ قائمة بالسلاسل واجمع القائمة.
val list2 = listOf("a", "bbb", "cc")
println(list2.sum())
⇒ error: none of the following functions can be called with the arguments supplied:
  1. إذا كان العنصر لا يعرف List أنّه يمكن جمعه مباشرةً، مثل سلسلة، يمكنك تحديد كيفية جمعه باستخدام .sumBy() مع دالة lambda، على سبيل المثال، الجمع حسب طول كل سلسلة. الاسم التلقائي لوسيطة lambda هو it، ويشير it هنا إلى كل عنصر في القائمة أثناء اجتياز القائمة.
val list2 = listOf("a", "bbb", "cc")
println(list2.sumBy { it.length })
⇒ 6
  1. وهناك المزيد يمكنك تنفيذه باستخدام القوائم. لمعرفة إحدى الوظائف المتاحة، يمكنك إنشاء قائمة في IntelliJ IDEA، وإضافة النقطة، ثم الاطّلاع على قائمة الإكمال التلقائي في التلميح. يصلح استخدام أي عنصر. جرِّب هذه القائمة باستخدام القائمة.

  1. اختَر listIterator() من القائمة، ثم ابحث في القائمة عن عبارة for واطبع جميع العناصر مفصولة بمسافات.
val list2 = listOf("a", "bbb", "cc")
for (s in list2.listIterator()) {
    println("$s ")
}
⇒ a bbb cc

الخطوة 2: تجربة خرائط التجزئة

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

  1. أنشئ خريطة تجزئة تتطابق مع الأعراض والمفاتيح وأمراض السمك والقيم.
val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
  1. يمكنك بعد ذلك استرداد قيمة المرض استنادًا إلى مفتاح الأعراض، باستخدام get() أو حتى الأقواس المربّعة الأقصر [].
println(cures.get("white spots"))
⇒ Ich
println(cures["red sores"])
⇒ hole disease
  1. حاوِل تحديد أعراض ليست على الخريطة.
println(cures["scale loss"])
⇒ null

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

  1. حاوِل البحث عن مفتاح غير متطابق باستخدام getOrDefault().
println(cures.getOrDefault("bloating", "sorry, I don't know"))
⇒ sorry, I don't know

إذا كنت بحاجة إلى أكثر من مجرد عرض قيمة، يوفّر Kotlin الدالة getOrElse().

  1. غيِّر الرمز لاستخدام getOrElse() بدلاً من getOrDefault().
println(cures.getOrElse("bloating") {"No cure for this"})
⇒ No cure for this

بدلاً من عرض قيمة تلقائية بسيطة، يتم تنفيذ أي رمز بين الأقواس المعقوفة {}. في المثال، تعرض الدالة else سلسلة، ولكن قد يكون الأمر رائعًا مثل العثور على صفحة ويب لها علاج وعرضها.

تمامًا مثل mutableListOf، يمكنك أيضًا إنشاء mutableMapOf. تتيح لك الخريطة القابلة للتغيير وضع العناصر وإزالتها. تعني كلمة "تغيير" إمكانية التغيير والتغيير، أي القدرة على التغيير.

  1. يمكنك إنشاء خريطة مستودع يمكن تعديلها، وربط سلسلة معدات بعدد العناصر. يمكنك إنشاء قناة باستخدام شبكة أسماك، ثم إضافة 3 أجهزة تنظيف للخزانات إلى المستودع باستخدام put()، وإزالة شبكة السمك باستخدام remove().
val inventory = mutableMapOf("fish net" to 1)
inventory.put("tank scrubber", 3)
println(inventory.toString())
inventory.remove("fish net")
println(inventory.toString())
⇒ {fish net=1, tank scrubber=3}{tank scrubber=3}

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

الخطوة 1: التعرّف على الثوابت مقابل المقارنة

  1. في REPL، حاول إنشاء ثابت رقمي. في لغة Kotlin، يمكنك إنشاء ثوابت من المستوى الأعلى وتحديد قيمة لها في وقت التجميع باستخدام const val.
const val rocks = 3

يتم تحديد القيمة، ولا يمكن تغييرها، وهو ما يشبه كثيرًا الإعلان عن val عادي. ما الفرق بين const val وval؟ يتم تحديد قيمة const val في وقت التجميع، حيث يتم تحديد قيمة val أثناء تنفيذ البرنامج، ما يعني أنه يمكن تخصيص val من خلال دالة في وقت التشغيل.

وهذا يعني أنّه يمكن تخصيص قيمة من الدالة من خلال val، ولكن لا يمكن ضبط const val.

val value1 = complexFunctionCall() // OK
const val CONSTANT1 = complexFunctionCall() // NOT ok

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

object Constants {
    const val CONSTANT2 = "object constant"
}
val foo = Constants.CONSTANT2

الخطوة 2: إنشاء كائن مصاحب

لا تمتلك لغة Kotlin مفهومًا للثوابت على مستوى الصف.

لتحديد ثوابت في صف، يجب تضمينها في كائنات مصاحب مُعلنة باستخدام الكلمة الرئيسية companion. الكائن المصاحب هو في الأساس كائن فردي ضمن الفئة.

  1. أنشئ فئة تحتوي على كائن مصاحب يحتوي على ثابت سلسلة.
class MyClass {
    companion object {
        const val CONSTANT3 = "constant in companion"
    }
}

الفرق الأساسي بين العناصر المصاحبة والعناصر العادية هو:

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

وهناك المزيد، ولكن كل ما تحتاج إلى معرفته الآن هو التفاف الثوابت في صفوف في كائن مصاحب.

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

تسمح لك دوال الإضافات بإضافة وظائف إلى صف حالي بدون الحاجة إلى الوصول إلى رمز المصدر المتعلق به. على سبيل المثال، يمكنك الإعلان عنها في ملف Extension.kt الذي يُعد جزءًا من حزمتك. ولا يؤدي ذلك إلى تعديل الفئة، ولكنه يسمح لك باستخدام رمز النقطة عند استدعاء الدالة على عناصر من هذه الفئة.

الخطوة 1: كتابة دالة الإضافة

  1. لا تزال تعمل في REPL، واكتب وظيفة إضافة بسيطة، hasSpaces() للتحقق مما إذا كانت السلسلة تحتوي على مسافات. يكون اسم الدالة مسبوقًا بالفئة التي تعمل فيها. داخل الدالة، تشير this إلى الكائن الذي تستدعيه، ويشير it إلى المكرر في استدعاء find().
fun String.hasSpaces(): Boolean {
    val found = this.find { it == ' ' }
    return found != null
}
println("Does it have spaces?".hasSpaces())
⇒ true
  1. يمكنك تبسيط الدالة hasSpaces(). ليست هناك حاجة إلى this بشكل صريح، ويمكن تقليل الدالة إلى تعبير واحد وعرضها، لذا فإن الأقواس المنحنية حول {} ليست مطلوبة أيضًا.
fun String.hasSpaces() = find { it == ' ' } != null

الخطوة 2: التعرُّف على حدود الإضافات

لا تستطيع وظائف الإضافات سوى الوصول إلى واجهة برمجة التطبيقات العامة للصف الدراسي الذي تديره الطلاب. لا يمكن الوصول إلى المتغيّرات private.

  1. جرِّب إضافة دوال الإضافات إلى موقع تم وضع علامة private عليه.
class AquariumPlant(val color: String, private val size: Int)

fun AquariumPlant.isRed() = color == "red"    // OK
fun AquariumPlant.isBig() = size > 50         // gives error
⇒ error: cannot access 'size': it is private in 'AquariumPlant'
  1. افحص الرمز أدناه وتعرّف على ما ستتم طباعته.
open class AquariumPlant(val color: String, private val size: Int)

class GreenLeafyPlant(size: Int) : AquariumPlant("green", size)

fun AquariumPlant.print() = println("AquariumPlant")
fun GreenLeafyPlant.print() = println("GreenLeafyPlant")

val plant = GreenLeafyPlant(size = 10)
plant.print()
println("\n")
val aquariumPlant: AquariumPlant = plant
aquariumPlant.print()  // what will it print?
⇒ GreenLeafyPlant
AquariumPlant

plant.print() يطبع GreenLeafyPlant. قد تتوقّع أن يطبع aquariumPlant.print() الرمز GreenLeafyPlant أيضًا، لأنه تم تخصيص القيمة plant له. ولكن يتم التعامل مع النوع عند التجميع، لذلك تتم طباعة AquariumPlant.

الخطوة 3: إدراج موقع للإضافة

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

  1. لا تزال تعمل في REPL، أضِف خاصية الإضافة isGreen إلى AquariumPlant، وهي true إذا كان اللون أخضر.
val AquariumPlant.isGreen: Boolean
   get() = color == "green"

يمكن الوصول إلى الخاصية isGreen تمامًا مثل الموقع الإلكتروني العادي. وعند الوصول إليها، يتم استدعاء قيمة isGreen للحصول على القيمة.

  1. اطبع السمة isGreen للمتغيّر aquariumPlant ولاحظ النتيجة.
aquariumPlant.isGreen
⇒ res4: kotlin.Boolean = true

الخطوة 4: التعرّف على أجهزة الاستقبال القابلة للإخراج

يُطلق على الصف الذي يتم تمديده اسم المستلم، ويمكن جعل هذه الفئة فارغة. إذا أجريت ذلك، يمكن أن يكون المتغيّر this المستخدَم في النص الأساسي null، لذا تأكّد من اختباره. قد ترغب في استقبال مستقبِل فارغ، إذا كنت تتوقع أن يرغب المتصلون في استدعاء طريقة الإضافة على المتغيّرات الفارغة، أو إذا كنت تريد تقديم سلوك تلقائي عندما يتم تطبيق الدالة على null.

  1. لا يزال العمل في REPL يعمل على تحديد طريقة pull() التي تستقبل جهاز استقبال فارغًا. تتم الإشارة إلى ذلك بعلامة استفهام ? بعد النوع، قبل النقطة. داخل الجسم، يمكنك اختبار ما إذا كان this ليس null باستخدام علامة الاستفهام-تطبيق ?.apply.
fun AquariumPlant?.pull() {
   this?.apply {
       println("removing $this")
   }
}

val plant: AquariumPlant? = null
plant.pull()
  1. وفي هذه الحالة، لن تظهر أي نتيجة عند تشغيل البرنامج. بما أن plant هو null، لا يتم استدعاء println() الداخلي.

إنّ وظائف الإضافات فعّالة جدًا، ويتم تنفيذ معظم مكتبة Kotlin العادية كدوال إضافات.

في هذا الدرس، تعلّمت المزيد عن المجموعات وتعرّفت على الثوابت وتعرّفت على أهمية وظائف الإضافات وخصائصها.

  • يمكن استخدام الأزواج والثلاثيات لعرض أكثر من قيمة من دالة. على سبيل المثال:
    val twoLists = fish.partition { isFreshWater(it) }
  • تشتمل لغة Kotlin على العديد من الوظائف المفيدة لـ List، مثل reversed() وcontains() وsubList().
  • يمكن استخدام HashMap لربط المفاتيح بالقيم. على سبيل المثال:
    val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
  • تحديد الثوابت في وقت التجميع باستخدام الكلمة الرئيسية const يمكنك وضعها في أعلى مستوى، أو تنظيمها في كائن فردي، أو وضعها في كائن مصاحب.
  • الكائن المصاحب هو كائن فردي ضمن تعريف الفئة، ويتم تحديده باستخدام الكلمة الرئيسية companion.
  • دوال الإضافات وخصائصها يمكنها إضافة وظائف إلى الصف. على سبيل المثال:
    fun String.hasSpaces() = find { it == ' ' } != null
  • يتيح لك جهاز الاستقبال القابل للإنشاء إنشاء إضافات في صف يمكن أن تكون null. يمكن إقران عامل التشغيل ?. مع apply للتحقق من null قبل تنفيذ الرمز. على سبيل المثال:
    this?.apply { println("removing $this") }

مستندات Kotlin

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

برامج تعليمية بلغة Kotlin

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

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

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

IntelliJ IDEA

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

يسرد هذا القسم المهام الدراسية المحتملة للطلاب الذين يعملون من خلال هذا الدرس التطبيقي حول الترميز في إطار دورة تدريبية يُديرها معلِّم. يجب أن ينفِّذ المعلّم ما يلي:

  • يمكنك تخصيص واجب منزلي إذا لزم الأمر.
  • التواصل مع الطلاب بشأن كيفية إرسال الواجبات المنزلية
  • وضع درجات للواجبات المنزلية.

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

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

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

السؤال 1

أي من الخيارات التالية يعرض نسخة من القائمة؟

add()

remove()

reversed()

contains()

السؤال 2

أي من وظائف الإضافات التالية على class AquariumPlant(val color: String, val size: Int, private val cost: Double, val leafy: Boolean) سيؤدي إلى حدوث خطأ في برنامج التجميع؟

fun AquariumPlant.isRed() = color == "red"

fun AquariumPlant.isBig() = size > 45

fun AquariumPlant.isExpensive() = cost > 10.00

fun AquariumPlant.isNotLeafy() = leafy == false

السؤال 3

أي مما يلي ليس مكانًا يمكنك من خلاله تحديد ثوابت باستخدام const val؟

▢ على المستوى الأعلى لملف

▢ في الصفوف العادية

▢ بالكائنات المفردة

▢ في الكائنات المصاحبة

انتقل إلى الدرس التالي: 5.2 Generals

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