Kotlin Bootcamp for Programmers 5.1: Extensions

این Codelab بخشی از دوره Kotlin Bootcamp برای برنامه نویسان است . اگر به ترتیب روی کدها کار کنید، بیشترین ارزش را از این دوره خواهید گرفت. بسته به دانش خود، ممکن است بتوانید برخی از بخش ها را مرور کنید. این دوره برای برنامه نویسانی است که زبان شی گرا را می دانند و می خواهند Kotlin را یاد بگیرند.

مقدمه

در این Codelab شما با تعدادی از ویژگی های مفید مختلف در Kotlin از جمله جفت ها، مجموعه ها و توابع افزونه آشنا می شوید.

به جای ساختن یک برنامه نمونه واحد، درس‌های این دوره برای ایجاد دانش شما طراحی شده‌اند، اما نیمه مستقل از یکدیگر باشند تا بتوانید بخش‌هایی را که با آن‌ها آشنا هستید، مرور کنید. برای گره زدن آنها به یکدیگر، بسیاری از نمونه ها از تم آکواریوم استفاده می کنند. و اگر می خواهید داستان کامل آکواریوم را ببینید، دوره Kotlin Bootcamp for Programmers Udacity را بررسی کنید.

آنچه از قبل باید بدانید

  • نحو توابع، کلاس ها و متدهای کاتلین
  • نحوه کار با Kotlin's REPL (Read-Eval-Print Loop) در IntelliJ IDEA
  • نحوه ایجاد یک کلاس جدید در IntelliJ IDEA و اجرای یک برنامه

چیزی که یاد خواهید گرفت

  • نحوه کار با جفت و سه تایی
  • اطلاعات بیشتر در مورد مجموعه ها
  • تعریف و استفاده از ثابت ها
  • نوشتن توابع پسوند

کاری که خواهی کرد

  • درباره نقشه‌های جفت، سه‌گانه و هش در REPL بیاموزید
  • روش های مختلف سازماندهی ثابت ها را بیاموزید
  • یک تابع پسوند و یک ویژگی پسوند بنویسید

در این کار با جفت ها و سه گانه ها و تخریب ساختار آنها آشنا می شوید. جفت ها و تریپل ها کلاس های داده از پیش ساخته شده برای 2 یا 3 آیتم عمومی هستند. به عنوان مثال، این می تواند برای داشتن یک تابع بیش از یک مقدار مفید باشد.

فرض کنید شما یک List از ماهی دارید، و یک تابع isFreshWater() برای بررسی اینکه آیا ماهی یک ماهی آب شیرین است یا آب شور است. List.partition() دو لیست را برمی گرداند، یکی با مواردی که شرط true است و دیگری برای مواردی که شرط false است.

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

مرحله 1: چند جفت و سه تا درست کنید

  1. REPL ( Tools > Kotlin > Kotlin REPL ) را باز کنید.
  2. یک جفت ایجاد کنید، یک قطعه از تجهیزات را با آنچه که برای آن استفاده می شود مرتبط کنید، سپس مقادیر را چاپ کنید. می‌توانید با ایجاد عبارتی که دو مقدار را به هم متصل می‌کند، مانند دو رشته، یک جفت ایجاد کنید، سپس از .second .first to به هر مقدار استفاده کنید.
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 مقدار یک تریپل ایجاد می کنید. برای اشاره به هر مقدار از .third .first .second .
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: چند جفت و سه تایی را تخریب کنید

جداسازی جفت ها و سه تایی ها به قسمت های آنها را تخریب می گویند. جفت یا سه تایی را به تعداد مناسبی از متغیرها اختصاص دهید و کاتلین ارزش هر قسمت را به ترتیب تعیین می کند.

  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() با یک تابع لامبدا مشخص کنید، برای مثال، برای جمع کردن طول هر رشته. نام پیش‌فرض یک آرگومان لامبدا 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}

در این کار با ثابت ها در کاتلین و روش های مختلف سازماندهی آنها آشنا می شوید.

مرحله 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: یک شیء همراه ایجاد کنید

کاتلین مفهومی از ثابت های سطح کلاس ندارد.

برای تعریف ثابت ها در داخل یک کلاس، باید آن ها را در اشیاء همراهی که با کلمه کلیدی companion اعلام شده اند قرار دهید. شیء همراه اساساً یک شیء تکی در کلاس است.

  1. یک کلاس با یک شی همراه حاوی یک ثابت رشته ایجاد کنید.
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: با محدودیت های افزونه ها آشنا شوید

توابع برنامه افزودنی فقط به API عمومی کلاسی که در حال گسترش آن هستند دسترسی دارند. متغیرهایی که 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: در مورد گیرنده های nullable بدانید

کلاسی که گسترش می دهید گیرنده نامیده می شود و می توان آن کلاس را باطل کرد. اگر این کار را انجام دهید، this متغیر مورد استفاده در بدنه می تواند null ، بنابراین مطمئن شوید که آن را آزمایش کنید. اگر انتظار دارید که تماس گیرندگان بخواهند متد افزونه شما را بر روی متغیرهای nullable فراخوانی کنند، یا اگر می خواهید زمانی که تابع شما روی null اعمال می شود یک رفتار پیش فرض ارائه دهید، می خواهید یک گیرنده null بگیرید.

  1. هنوز در REPL کار می کنید، یک متد pull() تعریف کنید که یک گیرنده تهی می گیرد. این با علامت سوال مشخص می شود ? بعد از نوع، قبل از نقطه. در داخل بدنه، می توانید با استفاده از questionmark-dot-apply ?.apply. تست کنید که آیا this null نیست.
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( reversed() ، contain( contains() و subList() .
  • از HashMap می توان برای نگاشت کلیدها به مقادیر استفاده کرد. مثلا:
    val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
  • ثابت های زمان کامپایل را با استفاده از کلمه کلیدی const اعلام کنید. می‌توانید آن‌ها را در سطح بالایی قرار دهید، آنها را در یک شی تک‌تنی سازمان‌دهی کنید، یا در یک شیء همراه قرار دهید.
  • یک شیء همراه، یک شیء تکی در یک تعریف کلاس است که با کلمه کلیدی companion تعریف شده است.
  • توابع و ویژگی های برنامه افزودنی می توانند عملکردی را به یک کلاس اضافه کنند. مثلا:
    fun String.hasSpaces() = find { it == ' ' } != null
  • یک گیرنده nullable به شما امکان می دهد پسوندهایی را روی یک کلاس ایجاد کنید که می تواند null . ?. عملگر را می توان با application جفت کرد apply قبل از اجرای کد، وجود null را بررسی کند. مثلا:
    this?.apply { println("removing $this") }

مستندات کاتلین

اگر در مورد هر موضوعی در این دوره اطلاعات بیشتری می خواهید، یا اگر گیر کرده اید، https://kotlinlang.org بهترین نقطه شروع شما است.

آموزش های کاتلین

وب‌سایت https://try.kotlinlang.org شامل آموزش‌های غنی به نام Kotlin Koans، یک مترجم مبتنی بر وب و مجموعه کاملی از مستندات مرجع با مثال است.

دوره جسارت

برای مشاهده دوره Udacity در مورد این موضوع، به Kotlin Bootcamp for Programmers مراجعه کنید.

ایده IntelliJ

اسناد 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 ژنریک

برای یک نمای کلی از دوره، از جمله پیوندهایی به دیگر کد لبه ها، به «کوتلین بوت کمپ برای برنامه نویسان: به دوره خوش آمدید» مراجعه کنید.