تابع توسعه — مکانیزم Kotlin که امکان افزودن متدهای جدید به کلاسهای موجود بدون وراثت و بدون تغییر کد منبع را فراهم میکند. تابع با پیشوند نوع گیرنده اعلان میشود و به عنوان متد معمولی این نوع فراخوانی میشود. طبق Kotlin Documentation, 2025، توابع توسعه به متدهای استاتیک با اولین پارامتر گیرنده کامپایل میشوند که سربار صفر را در زمان اجرا نسبت به فراخوانیهای معمولی تضمین میکند.
نکات اصلی
تابع توسعه تابعی است که خارج از کلاس اعلان میشود اما به عنوان متد آن فراخوانی میشود. این تابع نوع گیرندهای را میپذیرد که متد جدید به آن افزوده میشود. در داخل تابع، کلمه کلیدی this به نمونه نوع گیرنده اشاره میکند. این رویکرد امکان توسعه کلاسهای final، کلاسهای کتابخانههای شخص ثالث و حتی انواع اولیه را بدون ایجاد لفاف فراهم میکند.
برخلاف Java که برای افزودن متد به یک کلاس موجود یا به وراثت نیاز دارد یا به کلاس ابزاری استاتیک، Kotlin راهحلی ظریف بدون boilerplate ارائه میدهد. بر اساس دادههای تحقیق Kotlin Foundation (2024)، توابع توسعه در میان پنج ویژگی پرکاربرد زبان قرار دارند — 78% توسعهدهندگان Kotlin در کار روزانه خود از آنها استفاده میکنند.
کامپایلر Kotlin تابع توسعه را به یک متد استاتیک تبدیل میکند که در آن نمونه گیرنده به عنوان اولین آرگومان ارسال میشود. این بدان معناست که توابع توسعه بایت کد کلاس هدف را تغییر نمیدهند و کپسولهسازی را نقض نمیکنند — فقط فیلدها و متدهای عمومی گیرنده قابل دسترسی هستند.
از توابع توسعه برای نوشتن توابع ابزاری تمیز استفاده کنید که به طور منطقی به یک نوع خاص وابسته هستند اما نمیتوانند به کد منبع آن اضافه شوند.
نحو پایه تابع توسعه: نام نوع گیرنده، نقطه، نام تابع، سپس پارامترها و نوع بازگشتی. در داخل تابع، گیرنده از طریق this در دسترس است.
// تابع توسعه برای String
fun String.isEmail(): Boolean {
return this.contains("@") && this.contains(".")
}
// به عنوان متد معمولی String فراخوانی میشود
val result = "user@example.com".isEmail() // true
در این مثال isEmail() برای تمام رشتهها در دسترس میشود. در داخل تابع، this خود رشتهای است که متد روی آن فراخوانی میشود. Kotlin در بیشتر موارد اجازه حذف this را میدهد — میتوان به جای this.contains("@") فقط contains("@") نوشت.
توابع توسعه میتوانند پارامترهای اضافی دریافت کنند و مقادیر هر نوعی را برگردانند. این آنها را به توابع کاملی تبدیل میکند، نه فقط شکر نحوی.
fun List<Int>.defaultIfEmpty(default: Int): List<Int> {
if (this.isEmpty()) return listOf(default)
return this
}
val populated = listOf(1, 2).defaultIfEmpty(0)
val empty = listOf<Int>().defaultIfEmpty(0)
پارامتر default مقدار بازگشتی برای لیست خالی را تعیین میکند. تابع توسعه میتواند generic باشد — نوع List
توابع توسعه در Kotlin از توزیع ایستا استفاده میکنند، نه مجازی. این تفاوت کلیدی با متدهای معمولی کلاسهاست. اینکه کدام تابع توسعه فراخوانی میشود در مرحله کامپایل بر اساس نوع ایستای متغیر تعیین میشود، نه نوع واقعی آن در زمان اجرا.
open class Animal
class Dog : Animal()
fun Animal.speak() = "صدای حیوان"
fun Dog.speak() = "واق"
fun test() {
val animal: Animal = Dog()
println(animal.speak()) // «صدای حیوان» — نوع ایستا Animal
val dog: Dog = Dog()
println(dog.speak()) // «واق» — نوع ایستا Dog
}
با وجود اینکه متغیر animal به نمونه Dog اشاره میکند، تابع توسعه برای Animal فراخوانی میشود، زیرا نوع ایستای متغیر Animal است. اگر speak() یک متد مجازی کلاس بود، Dog.speak() فراخوانی میشد. این ویژگی هنگام طراحی API مبتنی بر توابع توسعه مهم است.
توابع توسعه عضو کلاس نیستند — آنها توابع معمولی هستند که برای استفاده در فایل دیگر نیاز به import دارند. Kotlin دو گزینه import ارائه میدهد: با نام تابع یا با تغییر نام.
// import با نام
import com.example.extensions.isEmail
// import با نام مستعار (برای حل تداخلها)
import com.example.extensions.isEmail as isValidEmail
// فراخوانی پس از import
val valid = "test@test.com".isEmail()
تغییر نام با as در تداخلات نام مفید است — مثلاً اگر دو کتابخانه تابع توسعه با نام یکسان برای یک نوع ارائه دهند. در این صورت میتوان یکی را با نام دیگر import کرد و از طریق نام جدید فراخوانی نمود.
توابع توسعه را میتوان در سطوح مختلف اعلان کرد:
| سطح | دید | مثال |
|---|---|---|
| Top-level | کل پروژه پس از import | fun String.isEmail() |
| Member extension | داخل کلاس مالک | class A { fun B.ext() } |
| Local | داخل تابع | fun test() { fun String.ext() } |
Member extension functions — حالت خاصی که تابع توسعه داخل کلاس دیگری اعلان میشود. در این حالت داخل تابع توسعه هم گیرنده (this تابع) و هم اعضای کلاس خارجی قابل دسترسی هستند.
اگر کلاس متدی با همان امضای تابع توسعه داشته باشد، همیشه عضو کلاس فراخوانی میشود. تابع توسعه هرگز متد کلاس را بازنویسی نمیکند — این یک تصمیم معماری برای جلوگیری از بازنویسیهای تصادفی است.
class User {
fun greet() = "سلام از کلاس"
}
fun User.greet() = "Hello from extension"
fun main() {
val user = User()
println(user.greet()) // «سلام از کلاس»
}
حتی اگر تابع توسعه بعداً تعریف شود و همان امضا را داشته باشد، کامپایلر متد کلاس را انتخاب میکند. کامپایلر Kotlin هنگام تشخیص چنین وضعیتی اخطار میدهد. تنها راه فراخوانی تابع توسعه در حضور متد کلاس، مراجعه به آن به عنوان یک تابع معمولی است: greet(user).
در توسعه Android، توابع توسعه به ابزاری استاندارد برای کار با View، Context و fragmentها تبدیل شدهاند. کتابخانه Android KTX بر روی توابع توسعه ساخته شده است و لفافهای مناسبی برای API اندروید ارائه میدهد.
// تابع توسعه برای کار با View
fun View.show() {
visibility = View.VISIBLE
}
fun View.hide() {
visibility = View.GONE
}
// تابع توسعه برای Context — toast
fun Context.toast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
// تابع توسعه به عنوان سازنده DSL
fun String.colorFormat(color: String): String =
"$color$this\u001b[0m"
تابع توسعه toast() فراخوانیهای وابسته به Context را مختصر میکند: به جای نوشتن Toast.makeText(context, message, length).show() کافی است context.toast("متن") بنویسید. این کار boilerplate را کاهش میدهد و کد را خواناتر میکند.
سوالات متداول
خیر، توابع توسعه از توزیع ایستا استفاده میکنند. اگر تابع توسعه برای کلاس پایه و همین طور برای زیرکلاس اعلان شود، اینکه کدام فراخوانی شود بر اساس نوع ایستای متغیر در مرحله کامپایل تعیین میشود، نه نوع واقعی در زمان اجرا.
میتوان توابع توسعه را برای انواع گیرنده nullable اعلان کرد: fun String?.isNullOrEmail(). در داخل چنین تابعی this میتواند null باشد، بنابراین باید از عملگرهای فراخوانی امن یا بررسی صریح null استفاده کرد.
خیر، توابع توسعه به متدهای استاتیک کامپایل میشوند. در سطح بایت کد، فراخوانی تابع توسعه با فراخوانی متد استاتیک با اولین پارامتر گیرنده یکسان است. هیچ انعکاس یا توزیع پویایی وجود ندارد.
بله، ویژگیهای توسعه مشابه توابع توسعه کار میکنند اما نمیتوانند حالت ذخیره کنند — فقط getter و setter. مثلاً: val List<Int>.sum get() = reduce(Int::plus). آنها نیز به متدهای استاتیک getter/setter کامپایل میشوند.
در Java مینویسید StringUtils.isEmail(str)، در Kotlin — str.isEmail(). تفاوت فقط در نحو نیست: توابع توسعه از تکمیل خودکار در IDE پشتیبانی میکنند، خوانایی زنجیرههای فراخوانی را بهبود میبخشند و به IDE امکان میدهند توابع مرتبط را برای نوع خاص پیشنهاد دهد. در Java چنین قابلیتهایی وجود ندارد.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید