مقالات باگتو

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

آیا تابحال احساس کرده اید که برخی از کارکردهای مفید در یک کلاس موجود از دست رفته اند؟

این کلاس می تواند در کتابخانه هایی باشند که توسط زبان نیز ارائه می شوند. 

"بله"

دقیقاً برای اضافه کردن آن قابلیت در کلاس چه کاری انجام می دهید؟

"کلاس را گسترش داده و متد را اضافه کنید و سپس از کلاس توسعه یافته خود استفاده کنید"در این صورت ،extension های Kotlin قطعاً می توانند زندگی شما را آسان تر کنند.

Kotlin امکان گسترش کلاس با قابلیت های جدید را بدون نیاز به ارث بردن از کلاس یا استفاده از هر نوع الگوی طراحی مانند Decoratoreasier فراهم می کند. این کار با استفاده از یک بیانیه خاص به نام extension ها انجام می شود این extension ها اساساً بدون افزایش کلاس، برخی عملکردها را در یک کلاس موجود اضافه می کنند.

چگونه از extension استفاده کنیم؟ 

در ادامه این را توضیح خواهم داد اما قبل از آن، به خاطر داشته باشید كه Kotlin از توابع extention و ویژگیهای آن (بدون زمینه پشتیبان) پشتیبانی می كند. بیایید آنها را یکی یکی بررسی کنیم.

توابع extension

برای تعریف یک تابع extention، ما نیاز داریم به پیشوند نام آن با یک نوع گیرنده، یعنی نوع در حال گسترش (extension) یا نام کلاس. بیایید بگوییم که می خواهیم تابعی را در اجرای List Interface اضافه کنیم که این عنصر را در وسط لیست قرار می دهد.

 

fun <T> List<T>.midElement(): T {
    if (isEmpty())
        throw NoSuchElementException("List is empty.")
    return this[size / 2]
}

//to call this method
var list = listOf<Int>(1, 2, 3, 4, 5)
var mid = list.midElement()
//or 
var arrayList = arrayListOf(5, 4, 3, 2, 1)
var mid = arrayList.midElement()

 

پس ما اینجا چیکار میکنیم؟

ما در اجرای لیست روشی به نام MidElement اضافه می کنیم که عنصر را در وسط لیست به ما باز می گرداند اگر این لیست خالی باشد ، NoSuchElementException را برمیگرداند. بعداً این روش midElement با هر شیء List معمولی فراخوانی می شود.

بیایید نمونه دیگری از کلاس Toast که بسیار مورد علاقه ما را در Android است بیاوریم.کلاس Toast برای نمایش یک پیام در نیمه پایینی صفحه در Android برای مدت طولانی یا کوتاه استفاده می شود. کد native برای نمایش یک Toast در اندروید است.

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

 

کد بالا یک متد استاتیک makeText از کلاس Toast را با سه پارامتر فراخوانی می کند (مرجع کلاس Context فعلی ، نمایش CharSequence و مدت زمان Int که Toast برای آن نشان داده می شود) متد استاتیک شیء کلاس Toast را که نمایش می دهیم برمی گرداند.

حالا برای اینکه فرا خوانی Toast از هر کلاس آسان باشد و کلاس Context را مستقیم یا غیر مستقیم extend کند، می توانیم یک extension method از کلاس Context ایجاد کنیم که دقیقاً همین کار را انجام دهد.

fun Context.showToast(text: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
        Toast.makeText(this, text, duration).show()
    }
//to call this method from any context extending class
    showToast("Hello")

 

رفع مشکلاتextension ها

استاتیک extension ها در واقع کلاس هایی را که extend می کنند را اصلاح نمی کنند. با تعریف یک extension، اعضای جدیدی را در یک کلاس وارد نمی کنید ، بلکه فقط توابع جدید را با علامت گذاری بر روی متغیرهایی از این نوع قابل فرا خوانی میکنید. توابع extention بصورت استاتیک ارسال می شوند .این بدان معناست که تابع فرمت که فراخوانی می شود با نوع عبارتی که در آن تابع فراخوانی می شود تعیین می شود، نه با توجه به نوع نتیجه ارزیابی آن عبارت در زمان اجرا. به طور خلاصه، آنها از نظر نوع گیرنده مجازی نیستند.

توضیحش را میتوانیم به اینصورت بیان کرد که ما دو کلاس داریم ، یک کلاس دیگر را گسترش می دهد و هر دو کارکردهای یکسانی با یک نام و امضاء مشابه دارند.



class DerivedClass : BaseClass()

fun BaseClass.someMethod(){
    print("BaseClass.someMethod")
}

fun DerivedClass.someMethod(){
    print("DerivedClass.someMethod")
}

 

حال اگر ما سعی کنیم با برخی از متدهای متضاد BaseClass علامت بزنیم اما reference واقعی ما به آن منتقل می شود یه reference از DerivedClass

fun printMessage(base : BaseClass){
        base.someMethod()
    }
//actual call
    printMessage(DerivedClass())

 

با این کار BaseClass.someMethod چاپ می شود.

اما چرا Reference DerivedClass را پاس کردم؟

از آنجا که extension function خوانده می شود ، فقط به نوع اعلام شده پارامتر بستگی دارد. به روش printMessage که کلاس BaseClass است. این متفاوت از پلی مورفیسم زمان اجرا است زیرا در اینجا بصورت ایستا برطرف می شود اما نه در زمان اجرا.

اگر کلاس من از همان روشی برخوردار باشد با همان امضایی که ما به عنوان یک extension function اضافه می کنیم چه می شود؟

کامپایلر متد اعلام شده در کلاس را می نامد نه extension function را.

class BaseClass{
        fun someMethod(){
            print("I am the actual someMethod")
        }
    }

    fun BaseClass.someMethod(){
        print("I am the extension function")
    }
//to call this method
    BaseClass().someMethod()

 

با این کار someMethod که در Base Class تعریف شده است فرا خوانی میشود و چاپ می شود.

گیرنده نامعتبر(Nullable Receiver)

extension ها را می توان با یک نوع nullable receiver نیز تعریف کرد. extension function را می توان در متغیر nullable فراخوانی کرد. بیایید مثالی از روش toString بیاوریم:

fun Any?.toString(): String {
    if (this == null) return "null"
    // after the null check,
    // 'this' is autocast to a non-null type, 
    // so the toString() below
    // resolves to the member function of the Any class
    return toString()
}
// not the above method can be call on a nullable variable also
var nullableVariable : Any? = null
nullableVariable.toString()

 

ویژگی های extension

به طور مشابه با functionها ، می توانیم خصوصیات extension را نیز اضافه کنیم.

val <T> List<T>.midIndex: Int
    get() = if (size == 0) 0 else size / 2

 

از آنجا که extension ها در واقع اعضایی را در کلاس ها قرار نمی دهند ، هیچ راهی کارآمد برای داشتن یک extension property از فیلد پشتیبان وجود ندارد. به همین دلیل initializer ها برای extension property مجاز نیست. رفتار آنها فقط با تهیه getters/setters قابل تعریف است.


    class BaseClass{
    }
    var BaseClass.index : Int = 10 //compile error
//Extension property cannot be initialized
//because it has no backing field

 

Companion Object Extension

ما همچنین می توانیم extension functions و properties را برای Companion object تعریف کنیم.

class BaseClass {

        companion object {
        }
    }

    fun BaseClass.Companion.companionMethod(){

    }
//to call this method
    BaseClass.companionMethod()

 

Scope of Extensions

بیشتر اوقات ما extension ها را در سطح بالا تعریف می کنیم ، یعنی مستقیماً تحت package ها.

  package com.example.extension
    fun BaseClass.extensionFunction(){

    }
//and use it with
    import com.example.extension.extensionFunction
// importing all extensions by name "extensionFunction"
// or
    import com.example.extension.*
    // importing everything from "com.example.extension"
    fun usage(base: BaseClass) {
        base.extensionFunction()
    }

 

امیدوارم که این آموزش برای شما مفید باشه.اگ مشکلی دارید میتونید در بخش نظرات مطرح کنید که ما پاسخگو باشیم.

تگ‌ها
اشتراک

0 نظرات