اگه یادتون باشه، ما توی ماژول buildSrc از scope فانکشن with استفاده کردیم. امروز میخوایم scope function ها رو بررسی کنیم و ببینم چطور با استفاده از اینا به کدی تمیز میرسیم.
کتابخونه استاندارد Kotlin پنج تا فانکشن داره که بهشون میگن Scope function که هدفشون ایجاد یه محدوده از یه object هستش. وقتی که اینا رو روی یه آبجکت call کنین با lambda، اینا یه محدوده موقت از اون آبجِکته رو براتون میسازن که دیگه شما میتونین بدون نام اون به شیء دسترسی پیدا کنین. این پنج تا فانکشن: let، run، with، apply و also اینا هستن.
اصولاً ، این فانکشن ها یه کاری رو انجام میدن: یه بلاک کد رو رو یه شی اجرا میکنن، ولی تفاوتشون اینه که چطوری شیء رو در داخل بلوک(block) در دسترس قرار می دن و نتیجه expression چیه.
ببینید scope fun ها درسته که کد شما رو میتونن مختصر تر کنن، ولی نباید بیش از حد از اینا استفاده کنیم، این میتونه خوندن کد شما رو سخت کنه و منجر به خطا شه. همچنین توصیه میشه از این توابع بصورت تو در تو استفاده نکنید و هنگام زنجیر زدن شون مراقب باشید چون به راحتی می میتونید در مورد context object فعلی شون و مقادیر it و this اشتباه بگیرید.
قدم اول فرق شون رو بررسی میکنیم و بعد با مثال، نحوه استفاده شون رو میبینیم.
از اونجاییکه scope فانکشن ها از نظر ماهیت شبیه هم هستن، درک تفاوت بینشون مهمه. دو تفاوت اصلی بین هر scope فانکشن وجود داره:
1- نحوه ای که اونا به context object اشاره میکنن.
2- مقدار برگشتی شون.
معنی context object: همون آبجکتی که scope فانکشن رو روش فراخوانی میکنیم.
توی لامبدایِ scope فانکشنمون، بجای اینکه اسم context object مون رو هی تکرار کنیم میایم از it و this استفاده میکنیم؛ حالا هر scope فانکشنی نحوه ارجاع دادنشون به scope فانکشن متفاوته: یه lambda receiver از this و یه lambda argument از it استفاده میکنه.
اولی: this
اسکوپ فانکش های run، with و apply کانتکس آبجکتشون رو بعنوان یه lambda receiver ارجاع میدن با واژه this.
داشتن context object بعنوان یه receiver (this) برای lambda هایی پیشنهاد میشه که عموما با member های آبجکت سروکار دارن.
val adam = Person("Adam").apply { age = 20 // same as this.age = 20 city = "London" } println(adam)
دومی: it
اسکوپ فانکش های let و also کانتکس آبجکتشون رو بعنوان یه lambda receiver ارجاع میدن با واژه it.
این مثل this نیست که صریحا به مثلا property های context object مون دسترسی داشته باشیم. ولی بهتره وقتی که از شیء بیشتر به عنوان آرگومان در تماس های تابع استفاده کردیم. همچنین این بهتره وقتی که شما از چندین متغیر در بلوک کد تون استفاده می کنید.
fun getRandomInt(): Int { return Random.nextInt(100).also { writeToLog("getRandomInt() generated value $it") } } val i = getRandomInt() println(i)
یک: apply و also خود "context object" رو برمیگردونن.
دو: let، run و with "نتیجه lamda" رو بر میگردونن.
** کانتکست آبجکت(context object)
چون مقدار برگشتی Apply و also خود خود context object هستش. میتونید اونها رو توی زنجیره فراخوانی(call chain) صدا بزنید: می توانید یکی پس از دیگری تماس های عملکرد زنجیره ای را در همان شیء ادامه دهید.
Code
ببینید، اول ما یه لیست خالی داریم و پاسش میدیم به also و یه message پرینت میکنه و لیست رو پاس میده به apply بعنوان context؛ توی apply به سری اعدا به لیست اضافه میشن و بعد apply این لیست تغییر یافته رو return میکنه و....
** نتیجه برگشتی
اسکوپ فانکنش های let، run و with نتیجه lambda رو برمیگردونن. میتونید هنگام اختصاص نتیجه به یه متغیر و .. از اینا استفاده کنید.
val countEndsWithE = numbers.run { add("four") add("five") count { it.endsWith("e") } } println("There are $countEndsWithE elements that end with e.")
میبینین که خط آخر رو برمیگردونه.
میتونیم از متد های (مثلا یه لیست) استفاده کنیم و یه temporary scope بسازیم.
معنیtemporary scope: مثلا اینجا متد های first و last رو ریختیم تو دوتا متغیر، این متد ها بعد از اجرا شدن این scope function از بین میرن.
val numbers = mutableListOf("one", "two", "three") val countEndsWithE = numbers.run { add("four") add("five") count { it.endsWith("e") } } println("There are $countEndsWithE elements that end with e.")
این بخش برای انتخاب مناسب هر scope فانکشن در جای مناسبشه.
1- میتونیم از let برای استفاده کردن یک یا چند فانکشن تو یه زنجیره فراخوانی استفاده کنیم.
بدون let اینجوریه:
val numbers = mutableListOf("one", "two", "three", "four", "five") val resultList = numbers.map { it.length }.filter { it > 3 } println(resultList)
ببینید اینجا اول ما یه لیست string رو اومدیم map کردیم بعد filter و بعد تویه یه متغیر قرار دادیم تا بعدا استفاده کنیم ازش.
با let هم اینجوری:
val numbers = mutableListOf("one", "two", "three", "four", "five") numbers.map { it.length }.filter { it > 3 }.let { println(it) // and more function calls if needed }
ولی وقتی از let استفاده میکنیم دیگه لازم نیست این عملیات رو بریزیم تو یه متغیر. اینجا هم اول ما یه لیست string رو اومدیم map کردیم بعد filter کردیم و هرچی که انجام داده بودیم رو لیست رو ریختیم توی let بجای اینکه بریزیم توی یه متغیر، الان توی let هر چندتا fun ای که میخوایم رو صدا میزنیم و استفاده میکنیم.
2- اگه یه تَک فانکش داشتیم میتونیم از "::" استفاده کنیم، اینجوری:
val numbers = mutableListOf("one", "two", "three", "four", "five") numbers.map { it.length }.filter { it > 3 }.let(::println)
3- با let میتونیم یه محدوده(scope) ای بسازیم که تو اون مقدار های non-null قرار بگیره.(این تو بهتر بنویسم قبلی بود):
val str: String? = "Hello" //processNonNullString(str) // compilation error: str can be null val length = str?.let { println("let() called on $it") processNonNullString(it) // OK: 'it' is not null inside '?.let { }' it.length }
4- میتونیم بجا اینکه بصورت زنجیروار مثلا اول .first() و context object رو بهمون بده و دنبالش تو let استفاده کنیم. میتونیم بجاش اینجوری کدمون رو ساده تر کنیم:
val numbers = listOf("one", "two", "three", "four") val modifiedFirstItem = numbers.first().let { firstItem -> println("The first item of the list is '$firstItem'") if (firstItem.length >= 5) firstItem else "!" + firstItem + "!" }.uppercase() println("First item after modifications: '$modifiedFirstItem'")
1- وقتی که نیازی به استفاده از نتیجه برگشتی ندارین، از with استفاده کنید:
val numbers = mutableListOf("one", "two", "three") with(numbers) { println("'with' is called with argument $this") println("It contains $size elements") }
2- میتونید بعنوان یه helper هم ازش استفاده کنید اینطوری که یه سری محاسبات انجام بدید و بعدا حالا ازش استفاده کنید.
val numbers = mutableListOf("one", "two", "three") val firstAndLast = with(numbers) { "The first element is ${first()}," + " the last element is ${last()}" } println(firstAndLast)
این فانکشن مثل with با این تفاوت که run یه extension فانکشنه.
1- وقتی که میخواین هم object ها رو initialize کنین و هم میخواین نتیجه برگشتی رو محاسبه کنید از run استفاده کنید.
val service = MultiportService("https://example.kotlinlang.org", 80) val result = service.run { port = 8080 query(prepareRequest() + " to port $port") } // the same code written with let() function: val letResult = service.let { it.port = 8080 it.query(it.prepareRequest() + " to port ${it.port}") }
2- میتونیم run رو روی یه آبجکت هم call نکنیم و بعنوان non-extension function ازش استفاده کنیم و همچنان نتیجه lambda رو برمیگردونه. وقتی که میخواین چندین statement رو call کنید تا اجرا بشه تو یه بلاک از استفاده بکنید:
val hexNumberRegex = run { val digits = "0-9" val hexDigits = "A-Fa-f" val sign = "+-" Regex("[$sign]?[$digits$hexDigits]+") } for (match in hexNumberRegex.findAll("+123 -FFFF !%*& 88 XYZ")) { println(match.value) }
اینجا ما regex رو initialize کردیم و بعنوان نتیجه برگردوندیم.
- هر وقت خواستین که یه سری چیزها رو به خود آبجکت نسبت بدین از apply استفاده کنید:
val adam = Person("Adam").apply { age = 32 city = "London" } println(adam)
دسترسیِ context object رو با it بهمون میده.
خود آبجکت رو برمیگردونه.
1- هر وقت خواستین یه سری عملیات رو روی خود آبجکتتون انجام بدین از also استفاده کنید.
val numbers = mutableListOf("one", "two", "three") numbers .also { println("The list elements before adding new one: $it") } .add("four")
خب امیدوارم خوشتون اومده باشه. اگه نظری، پیشنهادی، باگی چیزی بود لطفا تو کامنت ها به اشتراک بذارین.