جشنواره فطر سون لرن

آموزش گسترش توابع در کاتلین (kotlin extension function)

دسته بندی: آموزش
زمان مطالعه: 7 دقیقه
۰۶ فروردین ۱۳۹۹

آموزش گسترش توابع در کاتلین (kotlin extension function):

گاهی در هنگام برنامه‌نویسی به یک تابع یا قابلیت خاصی از یک کلاس نیاز پیدا می‌کنید که ممکن است در آن کلاس وجود نداشته باشد. این کلاس می‌تواند یکی از کلاس‌هایی باشد که توسط خود زبان ارائه می‌شود. برای اضافه کردن آن قابلیت در کلاس چه کاری انجام می‌دهید؟

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

در این مقاله در مورد یکی دیگر از ویژگی‌های جدید زبان kotlin به نام “Extension” صحبت خواهیم. با استفاده از گسترش توابع در کاتلین قادر خواهیم بود توابع و قابلیت‌های دلخواهمان را به کلاس مورد نظر حتی بدون وراثت و یا اصلاح آنها اضافه و یا حذف کنیم.

فهرست محتوای این مقاله

extension function:

همان‌طور که گفتیم برای افزودن یک تابع (function) یا قابلیت‌های یکی از کلاس‌های ارائه شده توسط زبان کاتلین مانند String و List و Toast و ... می‌توانیم از ویژگی Extension در این زبان استفاده کنیم. به تابعی که به کلاس موجود اضافه می‌شود و توسط آن ویژگی جدید به آن کلاس اضافه می‌شود، تابع گسترش (extension function) می‌گویند. و کلاس مرجعی که تابع گسترش به آن اضافه شده است Receiver Type نامیده می‌شود.

ایجاد یک تابع گسترش:

ایجاد یک تابع گسترش بسیار ساده است و تفاوت چندانی با ایجاد سایر توابع در پروژه ندارد. برای افزودن تابع گسترش به یک کلاس (Receiver Type) کافی است، تابع مورد نظر را در پروژه خود به صورت top level تعریف کنیم و هنگام تعریف تابع ابتدا نام کلاسی که قصد گسترش آن را داریم بیاوریم و سپس یک نقطه گذاشته و نام تابع گسترش را وارد کنیم و تعریف ورودی‌ها و خروجی و بدنه‌ی تابع همانند یک تابع معمولی در کاتلین است. تصویر پایین نحوه‌ی تعریف تابع گسترش و اجزای آن را نشان می‌دهد:

گسترش توابع در کاتلین

به عنوان مثال قصد داریم تابع گسترشی برای کلاس String (توسط خود زبان کاتلین ارائه می‌شود.) بسازیم که در ورودی آن یک حرف دریافت و تعداد تکرار آن در متن را به ما برگرداند. برای انجام این کار تابع repeatCount را به شکل زیر با پیشوند String تعریف می‌کنیم:

fun String.repeatCount(input: Char): Int {

    var repeatCount = 0
    for (c in this) {
        if (c.toLowerCase() == input.toLowerCase()) repeatCount++
    }
    return repeatCount
}

 

نکته: جهت فراخوانی تابع گسترش، در هنگام ایجاد یک تابع گسترش دقت کنید که آن را به صورت top level تعریف کنید(به طور مستقیم در یک فایل تعریف شود و درون کلاس دیگری قرار نگیرد).

فراخوانی تابع گسترش:

فراخوانی تابع گسترش هم بسیار ساده است و مطابق آنچه در تصویر 2 می‌بینید، همانند سایر توابع داخلی کلاس کافی است بعد از Receiver Object یک نقطه بگذارید و سپس نام تابع گسترش را وارد کنید.

گسترش توابع در کاتلین

 

مطابق آنچه در تصویر 2 مشاهده می‌کنید برای استفاده از این تابع و برای اینکه تعداد تکرار حرف 'd' را در عبارت "Android studio" بدست آوریم، به صورت زیر عمل می‌کنیم:

  fun main() {

      val strTest = "Android studio"

      val dCount :Int= strTest.repeatCount('D')

      println("D repeat $dCount times in $strTest")

  }
D repeat 3 times in Android studio

 

نکته‌ی قابل توجه هنگام تعریف از توابع گسترش این است که درون بدنه‌ی تابع می‌توانید به property و توابع کلاس مرجع (در مثال ما String) به طور مستقیم دسترسی پیدا کنید. درست مثل اینکه این تابع گسترش داخل همان کلاس تعریف شده است. اما نمی‌توانید به توابع و متغیرهای private و protected که داخل کلاس مرجع تعریف شده‌اند، دسترسی پیدا کنید.

فراخوانی کردن توابع گسترش در جاوا:

فراخوانی توابع گسترش در جاوا با فراخوانی آن در کلاس کاتلین متفاوت است. جهت فراخوانی توابع گسترش در جاوا ابتدا باید اسم پکیجی که تابع در آن تعریف شده با پسوند Kt را بیاوریم و سپس نقطه و در آخر نام تابع گسترش را وارد می‌کنیم.

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

برای مثال می‌خواهیم مثال قبل را این بار در یک کلاس جاوا فراخوانی کنیم. فرض کنید تابع repeatCount را در فایلی با نام EextensionTest.kt ساخته‌ایم حال برای فراخوانی تابع باید به صورت زیر عمل کنیم:

        String strReceiver = "Android Studio";
        ExtentionsTestKt.repeatCount(strReceiver, 'd');

ارث بری در گسترش:

تعریف توابع گسترش برای یک کلاس موجب اصلاح شدن آن کلاس نمی‌شود. در واقع با تعریف توابع گسترش، اعضای جدیدی را در یک کلاس وارد نمی‌کنید، بلکه فقط توابع جدیدی را ساخته‌اید که از طریق متغیرهایی که از جنس آن کلاس هستند(Receiver Object ها) قابل صدا زدن هستند.

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

به عنوان مثال برای نمایش یک Toast در کاتلین به طور معمول از کد زیر استفاده می‌شود:

Toast.makeText(this, “Hello”, Toast.LENGTH_SHORT).show()

حال ما می‌خواهیم به کلاس Context، که یکی از کلاس‌های سیستم عامل اندروید است تابعی را بیفزاییم و هرگاه یکی از کلاس‌هایی مانند اکتیویتی (activity) که از Context ارث می‌برند، متنی را در ورودی از ما بگیرد و به طور مستقیم آن را Toast کند. برای انجام این‌کار تابع گسترشی با نام showToast را به صورت زیر تعریف می‌کنیم:

fun Context.showToast(text: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
    Toast.makeText(this, text, duration).show()
}

مطابق با تعریف توابع گسترش انتظار داریم در هر یک از اکتیوتی‌های نرم افزار به صورت مستقیم بتوانیم showToast را به‌صورت زیر فراخوانی کنیم:

this.showToast("Hello world")

اما بر خلاف انتظارمان نمی‌توان از داخل یک اکتیویتی تابع showToast را فراخواند. و دلیل این امر هم این است که showToast فقط توسط نمونه‌های context قابل صدا زدن هستند و نمی‌توان آن را از طریق نمونه‌های اکتیویتی ( از context ارث می‌برد) صدا زد.

گسترش خصوصیات (Properties:)

با استفاده از ویژگی گسترش در زبان کاتلین همانند توابع یک کلاس، خصوصیات آن نیز قابل گسترش هستند. برای مثال می‌خواهیم خصوصیتی برای کلاس String ایجاد کنیم که آخرین حرف آن را برای ما برگرداند. بدین منظور متغیر lastChar را به‌صورت زیر تعریف می‌کنیم:

val String.lastChar: Char
get() = get(length - 1)

از آنجا که خصوصیات گسترش یک کلاس، به عنوان اعضای آن شناخته نمی‌شوند، به همین دلیل خصوصیات گسترش قابل مقداردهی اولیه نیستند و نمی‌توانند هیچ مقداری را برای کلاس ذخیره کنند و رفتار آنها فقط با استفاده از getter و setter قابل کنترل هست.

به عنوان مثال اگر بخواهیم مانند کد زیر یک مقدار را به آن بدهیم، خطا برمی‌گردد:

val House.number = 1 // error: initializers are not allowed for extension properties

گسترش Companion Object:

اگر کلاسی companion object داشته باشد شما می‌توانید برای companion object آن نیز توابع یا خصوصیت گسترش تعریف کنید که درست مانند اعضای معمولی آن فقط با استفاده از نام کلاس به عنوان یک qualifier آنها را فراخوانی کرد.
به عنوان مثال کلاس MyClass دارای companion object هست و قصد داریم تابع گسترش ()printCompanion را جهت چاپ عبارت "companion"، برای آن تعریف کنیم. برای این‌کار به صورت زیر عمل می‌کنیم:
class MyClass {
    companion object { }  // will be called "Companion"
}

fun MyClass.Companion.printCompanion() { println("companion") }
و جهت فراخوانی آن نیز کافی است همانند اعضای معمولی یک companion object ، همانند کد زیر عمل کنیم:
fun main() { 
   MyClass.printCompanion()
}
جمع بندی
همان‌طور که در طی مقاله بیان شد زمانی که به تابع یا قابلیت خاصی از یک کلاس، به‌خصوص کلاس‌هایی که توسط خود زبان ارائه می‌شود، نیاز پیدا کردید که در آن کلاس وجود نداشته باشد برای اضافه کردن آن قابلیت در کلاس توابع و خصوصیات گسترش می‌توانند راه‌حل مناسبی برای رفع این مشکل باشند. البته به این نکته توجه کنید که این نوع توابع و خصوصیات توسط نمونه‌های کلاس‌هایی که از کلاس مرجع شما ارث می‌برند قابل دسترسی نیستند و فقط زمانی از این نوع توابع استفاده کنید که قصد فراخوانی تابع از نمونه‌های کلاس مرجع را دارید و نه نمونه‌های کلاس‌هایی که از آن ارث می‌برند.

با شرکت در دوره‌ی آموزشی متخصص اندروید در کمتر از یکسال به یک توسعه‌دهنده اندروید همه فن حریف تبدیل می‌شوی که آماده‌ی استخدام، دریافت پروژه و حتی پیاده‌سازی اپلیکیشن خودت هستی.

چه امتیازی به این مقاله می دید؟
نویسنده علیرضا اسلمی

نظرات کاربران

سعید کیانپور

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

ارسال دیدگاه
خوشحال میشیم دیدگاه و یا تجربیات خودتون رو با ما در میون بذارید :