الإضافات (Extensions) في لغة Kotlin
الإضافات (Extensions)
توفِّر لغة Kotlin -كما هو الحال في لغات البرمجة مثل C# و Gosu- إمكانيّة الإضافة على الأصناف (classes) بوظائف جديدةٍ دون اللجوء إلى الوراثة (inheritance) منها أو استخدام أيّ أنماطٍ تصميميّةٍ مثل Secorator، وذلك من خلال تصريحات خاصّة تُدعى الإضافات (extensions)، إذ تدعم لغة Kotlin الدوال الإضافيّة (extension functions) والخاصّيّات الإضافيّة (extension properties).
الدوال الإضافيّة (Extension Functions)
لتعريف دالةٍ إضافيّةٍ يجب أن يُسبَق اسمها بنوع المستقبِل (receiver type) أي النوع الذي ستتمّ الإضافة عليه، ففي الشيفرة الآتية تُضاف الدالةُ swap
إلى MutableList<Int>
بالشكل:
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // يُعبَّر عن القائمة بالكلمة المفتاحيّة this
this[index1] = this[index2]
this[index2] = tmp
}
إذ تعبِّر الكلمة المفتاحية this
داخل الدالة الإضافيّة عن الكائن المستقبِل لها (والمتوضِع ما قبل النقطة .
)، وبعد الإضافة يمكن استدعاؤها من أي MutableList<Int>
مثل:
val l = mutableListOf(1, 2, 3)
l.swap(0, 2)
ففي الشيفرة السابقة تعبِّر الكلمة المفتاحيّة this
والموجودة داخل الدالةswap()
عن المتحول l
، ولتعميم تلك الدالة تصبح الشيفرة بالشكل:
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' تعبر عن القائمة
this[index1] = this[index2]
this[index2] = tmp
}
ويصرَّح عن النوع المُعمَّم (generic) قبل اسم الدالة لتصبح متاحةً في تعبير نوع المستقبِل (receiver type expression). (راجع الدوال المُعمَّمة [generic functions])
الفصل الستاتيكيّ للإضافات (Extensions are resolved statically)
لا تعدِّل الإضافات في الواقع الأصنافَ (classes) التي تُطبَّقُ عليها، إذ لا تُدخِل عناصر جديدةً ضمن الصنف بل تُضيف بعض الدوال القابلة للاستدعاء عبر صيغة النقطة .
في المتحوّلات من نوع هذا الصنف.
ومن المؤكَّد أنَ الدوال الإضافية منفصلةٌ بشكلٍ ستاتيكيّ أي أنّها ليست افتراضيّة (virtual) من نوع المستقبِل، وهذا يعني أنّ الدالة الإضافيّة تُحدَّد بنوع التعبير (expression) الذي تُستدعى فيه وليس بنوع النتيجة الصادرة عنه أثناء التنفيذ، مثل:
open class C
class D: C()
fun C.foo() = "c"
fun D.foo() = "d"
fun printFoo(c: C) {
println(c.foo())
}
printFoo(D())
ستُظهر هذه الشيفرة الخرج: "c" لأن الدالة الإضافيّة المُستدعاة تعتمد على النوع المُعرَّف فقط للمتحول c
والذي هو الصنف C
.
أما إن احتوى الصنف على دالتين لهما نفس الاسم ونوع المستقبِل وتقبلان نفس المتحولات (arguments) وكانت الأولى منهما دالةً من أصل الصنف (member function) والأخرى دالةً إضافيّةً (extension function) فإن الأولويّة ستكون للدالة الأصليّة الموجودة في الصنف (member function)، كما في الشيفرة الآتية:
class C {
fun foo() { println("member") }
}
fun C.foo() { println("extension") }
إن استدعاء الدالة c.foo()
من أي كائنٍ c
من نوع الصنف C
سيُظهر العبارة "member" وليس "extension".
ولكن يُسمح بالتحميل الزائد (overloading) على الدالة الأصليّة في الصنف (member function) من قِبل الدالة الإضافيّة إن كان لهما نفس الاسم ولكن بتعريفين مختلفين (من ناحية عدد المتحولات أو أنواعها)، مثل:
class C {
fun foo() { println("member") }
}
fun C.foo(i: Int) { println("extension") }
حينها سيُظهِر الاستدعاء c().foo(1)
العبارة "extension".