البرنامج التدريبي حول لغة Kotlin للمبرمجين 5.1: الإضافات

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

مقدمة

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

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

ما يجب معرفته

  • بنية دوال وفئات وطُرق Kotlin
  • كيفية استخدام REPL (حلقة القراءة والتقييم والطباعة) في Kotlin في IntelliJ IDEA
  • كيفية إنشاء صف جديد في IntelliJ IDEA وتشغيل برنامج

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

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

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

  • Learn about pairs, triples, and hash maps in the REPL
  • التعرّف على طرق مختلفة لتنظيم الثوابت
  • كتابة دالة إضافة وسمة إضافة

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

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

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

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

  1. افتح REPL (أدوات > Kotlin > 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.

الوظيفة

Purpose

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: التعرّف على الفرق بين const وval

  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 إلا على مستوى أعلى، وفي فئات singleton التي تم تعريفها باستخدام object، وليس مع الفئات العادية. يمكنك استخدام ذلك لإنشاء ملف أو عنصر فردي يحتوي على ثوابت فقط، واستيرادها حسب الحاجة.

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

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

لا تتضمّن لغة Kotlin مفهوم الثوابت على مستوى الفئة.

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

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

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

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

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

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

تتيح لك دوال الإضافة إضافة دوال إلى فئة حالية بدون الحاجة إلى الوصول إلى الرمز المصدر الخاص بها. على سبيل المثال، يمكنك تعريفها في ملف Extensions.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 تمامًا مثل أي سمة عادية. وعند الوصول إليها، يتم استدعاء الدالة getter الخاصة بالسمة 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 التدريبي للمبرمجين.

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 الأنواع العامة

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