by در Kotlin چیه ؟ -توضیح ساده با رویکرد حل مسئله

by در Kotlin چیه ؟ -توضیح ساده با رویکرد حل  مسئله

کلمه کلیدی by یکی از کاربردی ترین مواردی هست که در زبان برنامه نویسی کاتلین اضافه شده و به برنامه نویس های اندرویدی کمک های زیادی میکنه.

توی این مقاله به طور مفصل میخوایم راجب این موضوع توضیح بدیم.

by در کاتلین و برنامه نویسی اندروید

مقدمه‌ای بر رویکرد اشتباه در آموزش برنامه‌نویسی

در یادگیری برنامه‌نویسی، به نظر من همیشه یه مورد خیلی اشتباه آموزش داده میشه. اون چیه؟

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

مفهوم by هم یکی از همین مسئله‌هاست. پس ما توی این مطلب اول مشکل رو مطرح می‌کنیم، بعد راه‌حل رو با by ارائه می‌دیم.

سناریو اول : لاگ گرفتن و دیباگ کردن 

توی این سناریو ما میخوام بدونیم متغییر های که داریم ، چه موقع مقدار دهی میشن ، کی استفاده میشن ، کی تغییر میکنند

اصلاً چرا باید بفهمیم یک متغیر داره چی می‌کنه؟

  • برای دیباگ کردن: بدونیم کی و کِی مقدار متغیر تغییر کرده.

  • برای ثبت لاگ یا رفتارهای کاربر: مثلاً لاگ بگیریم که کاربر کی اسمش رو تغییر داده.

  • برای کنترل بیشتر: مثلاً قبل از تغییر مقدار، اعتبارسنجی (validation) انجام بدیم.

  • برای معماری MVVM: توی معماری‌هایی مثل MVVM، وقتی مقدار state تغییر می‌کنه، باید UI آپدیت بشه. پس کنترل تغییر خیلی مهمه.

خلاصه:

بدون کنترل روی مقداردهی، برنامه غیرقابل پیش‌بینی میشه و دیباگ‌ کردن سخت.

مرحله اول: تعریف مشکل

مثلاً ما یه متغیر معمولی تعریف می‌کنیم:

var a = 2

 خوبه! خیلی ساده و مستقیم

الان میخوام این متغییر زمانی که داره تغییر میکنه ، اینو رو من بدونم ، برای همین من میام کد رو به این صورت مینویسم

var a: Int = 2
    get() {
        println("Getting value $field")
        return field
    }
    set(value) {
        println("Setting value from $field to $value")
        field = value
    }

این جا باید یه توضیحی درمورد get و set بدم که در این جا چی کار میکنند :

اگه بخوای خیلی ساده بدونی:

  • get همون چیزیه که وقتی مقدار یک property (مثلاً یه val یا var) رو می‌خوای بخونی، اجرا میشه.

  • set هم موقعی اجرا میشه که می‌خوای مقدار اون property رو تغییر بدی.

پس چی شد ، هر تغییر که بخوایم روی این متغییر بدیم ، با این کد ها میشه از اون اطلاع پیدا کرد ،

حالا اگه در این سناریو ما چند بار و چندین متغییر داشته باشیم ، در یک کلاس ، اگه در کلاس های دیگه هم از این روش استفاده کنیم چی ؟
یه مثال ببینیم :

class UserForm {
    var name: String = ""
        get() {
            println("Getting name: $field")
            return field
        }
        set(value) {
            println("Setting name from $field to $value")
            field = value
        }

    var email: String = ""
        get() {
            println("Getting email: $field")
            return field
        }
        set(value) {
            println("Setting email from $field to $value")
            field = value
        }

    var age: Int = 0
        get() {
            println("Getting age: $field")
            return field
        }
        set(value) {
            println("Setting age from $field to $value")
            field = value
        }

    // و همین‌طور ادامه داره...
}

 معرفی راه‌حل — استفاده از by و تعریف delegate اختصاصی

خب، تا اینجا دیدیم که اگر بخوایم رفتار خاصی (مثل ثبت لاگ یا نظارت بر تغییر مقدار) برای چندین متغیر بنویسیم، باید کلی getter/setter دستی تکراری بنویسیم. این‌کار هم وقت‌گیره و هم کد رو زشت می‌کنه.

اینجاست که مفهوم delegation با by وارد میشه.

چی می‌خوایم بسازیم؟

ما می‌خوایم یه کلاسی بنویسیم به اسم مثلاً LoggingDelegate
که هر وقت یه متغیر تغییر کرد یا خونده شد، لاگ بگیره — بدون اینکه برای هر متغیر کد تکراری بنویسیم.

import kotlin.reflect.KProperty

class LoggingDelegate<T>(private var field: T) {

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        println("Getting '${property.name}' = $field")
        return field
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        println("Setting '${property.name}' from $field to $value")
        field = value
    }
}

توضیح کوتاه:

  • getValue زمانی صدا زده میشه که متغیر رو بخونی.

  • setValue زمانی صدا زده میشه که متغیر رو مقداردهی کنی.

  • property.name اسم متغیر رو به‌طور داینامیک می‌ده (مثلاً “name” یا “email”).

استفاده از by برای تعریف متغیرها

حالا بریم همون کلاس UserForm رو بازنویسی کنیم ولی این بار با by:

class UserForm {

    var name: String by LoggingDelegate("")

    var email: String by LoggingDelegate("")

    var age: Int by LoggingDelegate(0)

}

حالا اگه این کد رو اجرا کنی:

fun main() {

    val form = UserForm()

    form.name = "Ali"

    form.email = "[email protected]"

    println(form.name)

}

خروجی می‌شه:

Setting 'name' from  to Ali

Setting 'email' from  to [email protected]

Getting 'name' = Ali

یعنی دقیقاً همون کاری که اول با get و set دستی انجام می‌دادی، حالا فقط با یه خط by LoggingDelegate() انجام میشه! و بدون تکرار.

یه سناریو دیگه ای رو بررسی کنیم ،

سناریوی دیگر: استفاده از by viewModels() در Android

من یه کلاس ویو مدل دارم ، ساختم و میخوام در کلاس خودم که فرگمنت یا اکتیویتی یا در کامپوز یه اسکرین هست استفاده کنم .

بدون by viewModels() — نوشتن دستی ViewModel

class ProfileFragment : Fragment() {

    private var viewModel: ProfileViewModel? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        viewModel = ViewModelProvider(this).get(ProfileViewModel::class.java)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewModel?.loadProfile()
    }
}

مشکلات این روش:

  1. نیاز به late init یا nullable (?) چون نمی‌تونی مقداردهی مستقیم در سطح property انجام بدی.

  2. باید خودت مدیریت lifecycle رو انجام بدی. مثلاً this در ViewModelProvider(this) باید دقیق باشه وگرنه ViewModel اشتباهی می‌سازه.

  3. کد طولانی و تکراریه، مخصوصاً اگه توی چندین Fragment تکرار بشه.

  4. ممکنه اشتباه کنی و از requireActivity() یا parentFragment استفاده کنی و ViewModel اشتباهی بگیری.

حالت اشتباهی که باعث بروز باگ میشه

فرض کن یه نفر اشتباهی بنویسه:

viewModel = ViewModelProvider(requireActivity()).get(ProfileViewModel::class.java)

در حالی که هدفش ViewModel scoped به Fragment بوده.
در این صورت ViewModel تا وقتی Activity زنده‌ست باقی می‌مونه و این ممکنه:

  • باعث نشتی حافظه (Memory Leak) بشه.

  • یا باعث بشه data اشتباهی توی ViewModel باقی بمونه حتی بعد از destroy شدن Fragment.

حالا همون مثال با by viewModels()

class ProfileFragment : Fragment() {

    private val viewModel: ProfileViewModel by viewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewModel.loadProfile()
    }
}

خیلی خلاصه ، بدون مشکل ، و تمیز . 😎

نتیجه‌گیری نهایی و بیان جدول های جمع بندی کی از by استفاده کنیم، کی نه؟

زمان‌هایی که استفاده از by مناسبه:

کاربرد توضیح
✔ وقتی می‌خوای رفتاری مشترک برای چند property داشته باشی مثل ثبت لاگ، اعتبارسنجی، نوتیفای کردن UI و…
✔ وقتی می‌خوای lazy loading انجام بدی مثلاً متغیری فقط وقتی نیاز شد ساخته بشه (by lazy)
✔ وقتی می‌خوای تغییرات property رو مانیتور کنی مثل observable یا vetoable
✔ وقتی می‌خوای از کتابخونه‌ها یا فریم‌ورک‌ها کمک بگیری مثل by viewModels() در Jetpack یا by inject() در Koin
✔ برای کم کردن کدهای تکراری و تمیز نگه‌داشتن کلاس‌ها همون چیزی که تو با LoggingDelegate ساختی

 

زمان‌هایی که by استفاده نشه یا نیاز نیست:

کاربرد توضیح
❌ وقتی فقط یک property ساده داری و هیچ رفتاری نیاز نیست مثلاً یه var a = 5 ساده
❌ وقتی نمی‌خوای کدی پنهان باشه یا Debug سخت بشه delegate ممکنه رفتار پشت‌صحنه داشته باشه که برای بعضی‌ها گنگه
❌ وقتی نیاز به performance بسیار بالا داری و نمی‌خوای abstraction استفاده کنی هر abstraction ممکنه یه سربار کوچیکی ایجاد کنه

 

کلمه‌ی by در Kotlin یک ابزار قدرتمنده که اجازه می‌ده مسئولیت گرفتن یا ست کردن مقدار یک property رو به یک delegate بسپری. این باعث میشه:

  • کد تمیزتر بشه

  • تکرار کاهش پیدا کنه

  • رفتارهایی مثل لاگ‌گیری، lazy loading و… خیلی راحت پیاده‌سازی بشن

  • اشتباهات انسانی در lifecycle مدیریت ViewModel و… کاهش پیدا کنه

پس همیشه به این فکر کن که آیا واقعاً به کنترل روی property نیاز داری یا نه. اگه نیاز داری، by می‌تونه بهترین دوستت باشه.

پایان 🥰

دیدگاه‌ها ۰
ارسال دیدگاه جدید