متد Push() و متد Put() از کالکشن لاراول
متد Push() و متد Put() از کالکشن لاراول
2020-07-02
استفاده از Scope Funtions ها در کاتلین ( let , run , with , also , apply )

استفاده از Scope Funtions ها در کاتلین ( let , run , with , also , apply )

استفاده از توابع Scope یا Scope Functions در کاتلین (let، run، with، alsoو apply)، تمامی توسعه دهندگانی که از جاوا به کاتلین مهاجرت کرده اند قریب به اتفاق به این نتیجه رسیده اند که کاتلین برای توسعه بسیار راحت تر و سریع تر خواهد بود چرا که میزان کد کمتری به کار خواهد رفت و تمام توسعه دهندگان قطعا می دانند که کد کمتر برابر است با میزان خطای کمتر (Less Code Means Less Bugs).

استفاده از Scope Functions یا توابع Scope در کاتلین

یکی دیگر از امکانات کاتلین توابع Scope یا scope function هایی ست که در اختیار ما قرار می دهد.اگر مدتی است که با کاتلین کار می کنید قطعا به scope function های let , run , with , also , apply برخوردید و می دانید که چه عملکردی دارند.اما قطعا تفاوتی بین این scope ها هست و هرکدام را باید جای درست استفاده کرد که ممکن است تا به حال به این تفاوت توجه ای نکرده باشید، چرا که همه آنها در ظاهر یک عملکرد مشخص دارند اما شرایط خاصی که در اختیار ما قرار می دهند مهم و مطرح است که در این مقاله از تجاری اپ بطور کامل به آن میپردازیم.

مباحثی که در این مقاله از تجاری اپ به آن خواهیم پرداخت:

  • بطور کلی Scope functions یا توابع Scope در کاتلین چیست؟
  • تفاوت انواع Scope functions در کاتلین
  • خلاصه ای از Scope function ها

بطور کلی توابع Scope یا Scope functions در کاتلین چیست؟

به نقل از خود کاتلین:

By definition, Scoped functions are functions that execute a block of code within the context of an object.

بخواهیم به طور مختصر به معنی function scope بپردازیم می توانیم بگوییم: درواقع function scope ها یک محدوده یا بلاک را در اختیار ما قرار می دهند که بتوانیم دستورات خاصی را درون آن، با شرایط (context) خاص آن محدوده یا scope اجرا کنیم.به عبارت دیگر این scope ها یک بلاک با context موقت در اختیار توسعه دهنده قرار می دهد که بتواند دستورات خود را درون آن scope با context مشخص اجرا کند.در نهایت کد ما بسیار تمیز و مرتب خواهد بود چرا که تمام کدها ( بسته به نیاز ) بلاک بندی شده و هر بلاک کار خود را انجام خواهد داد.

تفاوت انواع توابع Scope یا Scope functions ها در کاتلین

در مجموع ما 5 نوع Scope function مختلف در کاتلین داریم: let، run، with، also و apply ،که در ادامه هرکدام را جداگانه با مثال مورد بررسی قرار خواهیم داد.اما پیش از آموزش مثال ها، فرض کنید ما یک مدل بصورت زیر داریم که درادامه برای تمامی مثال ها از این model استفاده می کنیم.

class Person() {
var name: String = "Abcd"
var contactNumber: String = "1234567890"
var address: String = "xyz"
fun displayInfo() = print("\n Name: $name\n " +
"Contact Number: $contactNumber\n " +
"Address: $address")
}

let

به مثال زیر دقت کنید:

private fun performLetOperation() {
val person = Person().let {
return@let "The name of the Person is: ${it.name}"
}
print(person)
}
output:
The name of the Person is: Abcd

همانگونه که از قطعه کد زیر مشخص است متد let به آبجکت person اختصاص داده شده و خروجی آن یک مقدار String است که البته خروجی می تواند هر مقدار از هرنوع دیگر هم باشد.به عبارت دیگر اپراتور let این موقعیت را فراهم می کند که یک سری عملیات برروی آبجکت جاری انجام شود و در نهایت یک خروجی با نوع مورد نظر return شود.

دستور reutn@let

توجه کنید که هیچ الزامی به نوشتن دستور reutn@let قبل از مقدار بازگشتی نیست. این دستور تنها برای خوانایی بیشتر کد نوشته شده .درصورتی که در کاتلین آخرین خط کد که به هیچ موقعیت خاصی اختصاص نداده باشد مقدار بازگشتی تلقی می شود.

برای مثال، مثال فوق بدین صورت می تواند نوشته شود:

private fun performLetOperation() {
val person = Person().let {
"The name of the Person is: ${it.name}"
}
print(person)
}
output:
The name of the Person is: Abcd

مثال فوق دقیقا همان عملکرد را خواهد داشت چرا که اخرین خط که print(person) است به هیچ state خاصی اختصاص داده نشده پس به عنوان مقدار خروجی let شناخته می شود.نکته دیگر درباره let این است که می توانیم خروجی آن را حذف کنیم طوری که let هیچ خروجی نداشته باشد.در آن صورت let مانند یک تابع عمل می کند که هیچ خروجی ندارد یا عبارتی از نوع Unit است.

private fun performLetOperation() {
val person = Person().let {
it.name = "NewName"
}
print(person)
}
output:
kotlin.Unit

همانگونه که می بینید دستور print همچنان کار میکند اما خروجی آن نوع Koltin.Unit است بدین معنی که هیچ مقداری ندارد.

دیگر مزایای let

اپراتور let به Context خود با it اشاره می کند (به صورت پیش فرض).

private fun performLetOperation() {
val person = Person().let { it ->
it.name = "NewName"
}
print(person)
}

که البته می توانیم جهت خوانایی و سادگی بیشتر نام it را به هر مقدار دیگری تغییر دهیم.

private fun performLetOperation() {
val person = Person().let { it ->
it.name = "NewName"
}
print(person)
}

این ویژگی زمانی مفید خواهد بود که ما چندین بلاک let تو در تو داشته باشیم و بخواهیم از Context هرکدام استفاده کنیم، خب طبیعتا اگر تمامی آنها it باشند به مشکل برخواهیم خورد پس این تغییر نام میتواند کمک بزرگی برای توسعه دهنده باشد.

ویژگی دوم let برای تشخیص و چک کردن null هاست که باعث می شود این عمل راحت تر انجام شود.خب نیاز به گفتن نیست که nullSafe بودن کاتلین برگ بنده آن است که در این بحث let این قابلیت را بطور کامل پشتیبانی می کند و nullSafe بودن خود را به صورت زیر حفظ میکند.

var name: String? = "Abcd"
private fun performLetOperation() {
val name = Person().name?.let {
"The name of the Person is: $it"
}
print(name)
}

فرض کنید که مقدار name می تواند null باشد یا به عبارتی nullable است و ما می خواهیم مقدار name را هم تنها درصورتی که null نباشد print کنیم پس می توانیم کد تمیز و مختصر فوق را بنویسیم که با let.? تشخیص می دهد که آیا مقدار name از آبجکت Person نال است یا خیر و اگر نال نبود به داخل بلاک رفته و مقدار باز گشتی معتبری print می شود.اما let حتی می تواند در دستورات زنجیروار یا chain call هم به صورت زیر کاربرد موثری داشته باشد.

fun main() {
val numbers = mutableListOf("One","Two","Three","Four","Five")
val resultsList = numbers.map { it.length }.filter { it > 3 }
print(resultsList)
}

در مثال فوق هدف ما این بوده که آیتم هایی که طول آنها بیشتر از ۳ است را درون یک متغیر دیگر قرار دهیم و در نهایت آن را print کنیم .اما می توانیم مثال فوق را به طور خلاصه تری با let به صورت زیر بنویسیم.

fun main() {
val numbers = mutableListOf("One","Two","Three","Four","Five")
numbers.map { it.length }.filter { it > 3 }.let {
print(it)
}
}

run

عملگر run همانند عملگر let در بخش برگرداندن (return) داده عمل می کند یعنی به این صورت که هم می تواند داده ای برنگرداند و هم می تواند داده از هرنوعی return کند. به همین خاطر معمولا دستور run برای مقدار دهی یک آبجکت به کار می رود که بتواند در انتهای دستورات مقداردهی یک مقدار مشخص هم برگرداند ( return ).

private fun performRunOperation() {
Person().run {
name = "iman"
contactNumber = "09123456789"
return@run "The details of the Person is: ${displayInfo()}"
}
}
output:
Name: Asdf
Contact Number: 0987654321
Address: xyz

همانطور که می بینید run scope به یک آبجکت اختصاص داده شده و درون بلاک آن تمامی فیلد های آن آبجکت مقداردهی شد و در انتها فانکشن displayInfo() مربوط به کلاس Person فراخوانی شده.

run در مقابل let

خب طبق توضیحات دستور run بسیار شبیه به let بود اما دلیل نوشتن این پست نیز درک تفاوت های همین دستوراتی ست که به ظاهر یک کار مشخص را انجام میدهند.

همانطور که گفته شد هردو let و run یک مقدار را return می کنند که می تواند از هر نوعی باشد.اما تفاوت دستورات let و run در چیست ؟
دستور run به context آبجکت با عبارت this اشاره می کند در حالی که دستور ‌let با عبارت it به context خود اشاره می کرد.
به همین خاطر است که در بلاک run ما از this.name استفاده نمی کنیم ( استفاده از دستور this اختیاری ست ) و تنها نوشتن خود name کفایت می کند چرا که بلاک run تشخیص میدهد اما در let شما باید بدین صورت به name اشاره کنید it.name .

اما دستور run بدلیل اینکه از this استفاده می کند این اجازه را از شما می گیرد که نام اشاره گر را از this به نام دیگری تغییر دهید اما در let شما می توانید نام it را به هر نام دیگری تغییر دهید که این مسئله در let های زنجیره ای یا تودرتو به شما کمک بزرگی در درک هر بلاک خواهند کرد.

Person().name?.let {some ->
"The name of the Person is: $some"
}

این نکته را هم درنظر داشته باشید که هردو let و run به طور کامل null checks هستند یعنی توانایی مدیریت کامل آبجکت های null را دارند.

val name = Person().name?.run {
"The name of the Person is: $this"
}
print(name)

with

دستور with بسیار شبیه به run است بلکه حتی از this به عنوان اشاره گر به context بلاک خود استفاده می کند.

private fun performWithOperation() {
val person = with(Person()) {
return@with "The name of the Person is: ${this.name}"
}
print(person)
}
Output:
The name of the Person is: Abcd

with در مقابل run

خب حال دستور run با این میزان شباهت به with چه تفاوت هایی با هم دارند؟
خب نقطه مهم و البته حیاتی در کدنویسی کاتلین بین این دو function scope اینجاست که دستور with قابلیت null check ندارد و این عمل باید بصورت دستی برای استفاده از هر فیلد در بلاک with پیاده شود.

استفاده از Scope Functions ها در کاتلین (let , run , with , also , apply)
استفاده از Scope Functions ها در کاتلین (let , run , with , also , apply)

همانطور که در تصویر میبینید ما یک خطا از سمت کاتلین داریم که احتمال null بودن مقدار name را می دهد و از ما می خواهد که با یکی از دستورات ? یا !! آن را کنترل کنیم.پس باید دستورات فوق را بدین صورت تغییر دهیم:

private fun performWithOperation() {
val person: Person? = null
with(person) {
this?.name = "asdf"
this?.contactNumber = "1234"
this?.address = "wasd"
this?.displayInfo()
}
}

برخلاف run که خود قابلیت null check داشت و کار را بسیار راحتتر میکرد( در بالا بصورت کامل توضیح دادیم ).

private fun performRunOperation() {
val person: Person? = null
person?.run {
name = "asdf"
contactNumber = "1234"
address = "wasd"
displayInfo()
}
}

apply

دستور apply تنها در بحث اشاره گر context که از this استفاده می کند و نه it به run‌ شبیه است .

private fun performApplyOperation() {
val person: Person? = null
person?.apply {
name = "iman"
contactNumber = "1234"
address = "tehran"
displayInfo()
}
}

استفاده های پرکاربرد از apply در برنامه نویسی اندروید، دستور apply مخصوصا در برنامه نویسی اندروید بسیار پرکاربر است.برای مثال زمان های زیادی پیش می آید که ما نیاز داریم یک نمونه از Intent یا Alert Dialog یا … را به همراه اعمال یک سری خصیصه ها (attribute) برگردانیم.برای مثال:

fun createIntent(intentData: String, intentAction: String): Intent {
val intent = Intent()
intent.action = intentAction
intent.data = Uri.parse(intentData)
return intent
}

مثال فوق به چشم بسیاری از توسعه دهندگان اندروید آشناست که یک تابع برای تولید یک Intent خاص پیاده شود.اما می توان تابع فوق را با استفاد از apply خیلی تمیزتر به صورت زیر پیاده کرد:

fun createIntent(intentData: String, intentAction: String) =
Intent().apply {
action = intentAction
data = Uri.parse(intentData)
}

همانطور که می بینید با استفاده از apply دیگر نیازی به ساختن یک نمونه از Intent و اعمال صفت ها به آن نیست پس در نهایت کد تمیزتر و بهینه تری خواهیم داشت.

apply در مقابل run

ما گفتیم که دو دستور apply و run به هم شباهت دارند اما تفاوت اصلی این دو دستور در نوع بازگشتی آنهاست.دستور run می تواند مقدار بازگشتی با هر نوعی داشته باشد در صورتی که دستور apply به صورت Unit است و نمی تواند بازگشتی داشته باشد.

استفاده از Scope Functions ها در کاتلین (let , run , with , also , apply)
استفاده از Scope Functions ها یا توابع Scope در کاتلین (let , run , with , also , apply)

also

متد also شبیه به let است اما تنها در بحث اشاره گر به Context که با عبارت it مشخص میشود و نه this .

private fun performAlsoOperation() {
val name = Person().also { currentPerson ->
print("Current name is: ${currentPerson.name}\n")
currentPerson.name = "modifiedName"
}.run {
"Modified name is: $name\n"
}
print(name)
}
output:
Current name is: Abcd
Modified name is: modifiedName

در مثال فوق مزیت استفاده از اشاره گر با نام دلخواه را متوجه می شوید.همانطور که می بینید به جای it از currentPerson برای خوانایی بیشتر استفاده کردیم.یکی از مزایای also این است که می توان از آن به صورت زنجیروار استفاده کرد.اما تصور کنید که ما مثال فوق را بدون استفاده از also پیاده می کردیم:

var name = Person().name
print("Current name is: $name\n")
name = "modifiedName"
name = name.run {
"Modified name is: $name\n"
}
print(name)

همانطور که می بینید از تمیزی و خوانایی کد کاسته شده درحالی که خروجی همان است.اما با استفاده از also می توان کد تمیزتر و readable تری داشت و همچنین می توان زنجیره های scope ها را به وجود آورد و کد را به حداقل رساند.

also در مقابل let

خب همانطور که گفته شد دستور also به let شباهت بسیار دارد اما تفاوت این دو function scope در چیست ؟
دستور also مقدار برگشتی نمی تواند داشته باشد و از نوع Unit است درحالی که let می تواند مقدار برگشتی با هرنوعی داشته باشد.

استفاده از Scope Functions ها در کاتلین (let , run , with , also , apply)
استفاده از Scope Functions ها یا توابع Scope در کاتلین (let , run , with , also , apply)

امیدوارم این آموزش برای شما مفید بوده باشد…

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *