قابلیت Lazy در کاتلین به عنوان یک «دیزاین پترن» (Design Pattern) در دنیای توسعه نرم‌افزار شناخته می‌شود که برنامه‌نویسان بسیاری نیز از آن استفاده می‌کنند. با استفاده از این قابلیت اشیا مورد استفاده در برنامه تنها در اولین دسترسی ایجاد و سپس در زمان فراخوانی مقداردهی اولیه می‌شوند. به زبان ساده استفاده از Lazy مقداردهی اولیه اشیا را به تاخیر می‌اندازد. در واقع، ویژگی Lazy این مورد را تضمین می‌کند که مقداردهی اولیه اشیا پرهزینه به جای زمان راه‌اندازی برنامه تنها در زمان فراخوانی صورت گیرد. بر همین اساس در این مطلب از مجله فرادرس نحوه تعریف و پیاده‌سازی Lazy در کاتلین مورد بررسی قرار خواهد گرفت.

علاوه بر این، با مطالعه این مطلب نکات مفیدی در خصوص چگونگی عملکرد Lazy، جایگزین‌های آن در کاتلین، تفاوت آن با Lateinit را نیز فرا خواهید گرفت. همچنین، برای درک بهتر این مفهوم سعی شده است تا چندین مثال کاربردی از نحوه استفاده و به کارگیری آن ارائه شود.

Lazy در کاتلین چیست؟

ویژگی Lazy در کاتلین قابلیتی بسیار مهم و کلیدی به منظور مقداردهی اولیه محسوب می‌شود. با استفاده از ویژگی Lazy در کاتلین می‌توان فرایند مقداردهی اولیه به متغیرها و توابع را به تاخیر انداخت. در نتیجه، برنامه‌های نوشته شده با استفاده از این ویژگی عملکرد بهتری ارائه می‌دهند.

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

Lazy در کاتلین چگونه کار می‌کند؟

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

برنامه‌نویس در حال کار و برنامه نویسی

مهم‌ترین نکات در خصوص استفاده از Lazy در کاتلین

برای استفاده از ویژگی Lazy در کاتلین باید نکات زیر را در نظر داشت.

  • استفاده از ویژگی Lazy در کاتلین از ایجاد اشیا غیرضروری جلوگیری می‌کند.
  • تنها باید از متغیرهای «غیر تهی» (nonnullable) در Lazy استفاده کرد.
  • امکان تعریف متغیر از نوع var هنگام کار با Lazy وجود ندارد.
  • در این روش شی مورد نظر تنها یک مرتبه مقداردهی اولیه می‌شود و بعد از آن مقدار شی از حافظه کش دریافت می‌شود.
  • شی مشخص شده تا زمان استفاده در برنامه مقداردهی نخواهد شد.

تعریف Lazy در کاتلین

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

برای تعریف Lazy باید از کلمه کلیدی by lazy

 به همراه تابع برای مقداردهی اولیه استفاده کرد. بعد از اولین دسترسی به مشخصه تعریف شده از نوع Lazy مقداردهی اولیه توسط تابع صورت می‌گیرد و مقدار درون حافظه ذخیره می‌شود. سپس، در دسترسی‌های بعدی مقدار ذخیره شده درون حافظه مورد استفاده قرار می‌گیرد. نمونه کد زیر نحوه تعریف متغیر از نوع Lazy و فراخوانی آن را نشان می‌دهد.

class kotlin {
val studName: String by lazy {
"www.example.com"
}
}
fun main () {
var obj = kotlin ();
println (obj.studName);
println ("We are calling the same object again" + obj.studName);
}

در مثال بالا، درون کلاس kotlin

 متغیر studName

 از نوع Lazy تعریف شده است. در این تعریف بعد از تعیین نوع متغیر از کلمه کلیدی lazy

 بعد از by

 استفاده شده است. سپس، درون تابع main

 شی obj

 از کلاس kotlin

 ساخته می‌شود. در ادامه خروجی حاصل از اجرای کد بالا آورده شده است.

www.example.com We are calling the same object again www.example.com

در زمان استفاده از Lazy باید به این نکته توجه داشت که مقدار متغیر در مرحله اول تخصیص داده می‌شود و نمی‌توان مجدداً مقدار جدیدی برای آن اختصاص داد.

تعریف Lazy به همراه متد Factory

تعریف مشخصه از نوع Lazy همیشه به سادگی و با مشخص کردن تابع برای مقداردهی اولیه صورت نمی‌پذیرد. گاهی اوقات فرایند تعریف مشخصه Lazy با پیچیدگی‌های بسیاری همراه است و به کد نویسی بیشتری نیاز دارد. در این شرایط می‌توان به کمک دیزاین پترن Factory پیاده‌سازی تابع مربوط به Lazy را به خارج از کلاس منتقل کرد. در نمونه کد زیر تابع مربوط به Lazy در خارج از کلاس مقداردهی شده است.

class TeamRepository(appSchedulers: AppSchedulers) {
  private val viewState by lazy(::createViewStateLiveData)

  private fun createViewStateLiveData(): LiveData<ViewState> =
      teamRepository.teamMembersStream()
          .map(::mapPresentingState)
          .onErrorReturn(::mapErrorState)
          .startWith(ViewState.Loading)
          .subscribeOn(appSchedulers.io)
          .observeOn(appSchedulers.main)
          .toLiveData()
}

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

پردازش همزمان چند رشته به کمک Lazy

تابع مربوط به Lazy تنها یک آرگومان با مقدار پیش‌فرض دارد که عملکرد آن را کنترل می‌کند. در صورت نیاز به دسترسی به چند «نخ | رشته» (Thread) به صورت همزمان در مشخصه تعریف شده از نوع Lazy باید از متد LazyThreadSafetyMode

 استفاده کرد. نمونه کد زیر نحوه پیاده‌سازی تابع Lazy برای دسترسی همزمان به چند Thread را نشان می‌دهد.

private val messageId by lazy(LazyThreadSafetyMode.NONE) { createMessageId() }

در صورتی که تابع Lazy تنها توسط یک Thread مورد دسترسی قرار گیرد باید از دستور LazyThreadSafetyMode.NONE

 استفاده کرد. همچنین، استفاده از دستور LazyThreadSafetyMode.PUBLICATION

 دسترسی چند Thread را به تابع Lazy امکان‌پذیر می‌کند. اکثر کدهای رابط کاربری نظیر اکتیویتی‌ها و فرگمنت‌ها بر روی Thread مربوط به UI اجرا می‌شوند و می‌توان برای آن‌ها از متد LazyThreadSafetyMode.NONE

 استفاده کرد.

آیا جایگزینی برای Lazy در کاتلین وجود دارد؟

استفاده از Lazy در کاتلین زمانی توصیه می‌شود که نیاز به ایجاد تاخیر در مقداردهی اولیه متغیرها یا مشخصه‌ها در برنامه وجود دارد. در واقع، منظور شرایطی است که مقداردهی اولیه متغیرها باید بعداً در جایی دیگر صورت گیرد. علاوه بر این، ویژگی Lateinit در کاتلین نیز وجود دارد که تضمینی در خصوص مقداردهی شدن متغیرها قبل از استفاده ارائه می‌دهد.

برنامه‌نویس در حال کار و برنامه نویسی

در صورتی که تنها تاخیر در مقداردهی اولیه متغیر مدنظر باشد، استفاده از Lazy نسبت به سایر گزینه‌های موجود منطقی‌تر است. استفاده از Lazy به خصوص در مقداردهی اولیه مشخصه‌های مربوط به رابط کاربری نظیر اکتیویتی و فرگمنت توصیه می‌شود. همچنین، در صورت نیاز به دسترسی مکرر به یک متغیر یا مشخصه بدون نیاز به بازیابی مجدد آن می‌توان از Lazy استفاده کرد. به عنوان مثال، برای خواندن مقدار از «اینتنت» (Intent) استفاده از Lazy توصیه می‌شود، زیرا در گام نخست مقدار از Intent دریافت شده و سپس برای دسترسی سریع‌تر از مقدار ذخیره استفاده می‌گردد. نمونه کد زیر نحوه استفاده از Lazy برای دریافت مقدار از Intent را نشان می‌دهد.

class OrderDetailActivity : FragmentActivity() {
  val orderId by lazy {
      intent.getParcelableExtra<OrderId>(EXTRA_ORDER_ID)
  }
}

کاربرد Lateinit در کاتلین چیست؟

در زبان برنامه نویسی کاتلین علاوه بر قابلیت Lazy روش دیگری تحت عنوان Lateinit نیز برای دادن مقدار اولیه با تاخیر به مشخصه‌ها و متغیرهای مختلف وجود دارد. کلمه کلیدی Lateinit خلاصه شده عبارت Late initalization است.

در این بخش نکاتی عنوان شده است که در خصوص استفاده از Lateinit باید به آن توجه داشت.

  • در این شیوه متغیر مقداردهی اولیه نخواهد شد و مقدار آن در طول برنامه تعیین می‌شود.
  • متغیر باید از نوع var تعریف شود.
  • متغیر نباید «تهی» (Null) باشد.
  • کلاس مربوط به متغیر نباید دارای متدهای getter و setter باشد.
  • باید از مقداردهی شدن متغیر در حین برنامه اطمینان حاصل کرد. در غیر این صورت اجرا برنامه با خطا مواجه خواهد شد.

نمونه کد زیر نحوه استفاده از Lateinit را نشان می‌دهد.

class lateinit
{
  lateinit var name : String
  fun init ()
  {
  if (this::name.isInitialized)
  println ("kotlin lazy");
else {
  name = "www.example.com/"
  println(this.name)
  }
  }
}
fun main() {
var obj=lateinit();
obj.init ();
}

خروجی حاصل از اجرا نمونه کد بالا به صورت زیر خواهد بود.

www.example.com/

مقداردهی نشدن متغیر Lateinit قبل از استفاده چه مشکلی دارد؟

کاربرد Lateinit در کاتلین این است که می‌توان مقداردهی اولیه متغیر را به زمان دیگری موکول کرد. با این حال باید به این نکته توجه داشت که متغیر تعریف شده از نوع Lateinit حتما قبل از فراخوانی و استفاده باید مقداردهی شود. برای درک بهتر این موضوع مثال زیر را در نظر بگیرید.

class MainActivity : AppCompatActivity() {

    //Here is the value we don't want to initialize at declaration time,
    // so we used the lateinit keyword.
    private lateinit var myUser:User

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //We can initialize our value at any time in our application.
        myUser = User("Hüseyin","Özkoç")
        println("MY NAME IS : " + myUser.name)
    }
}

data class User(var name: String, var surname: String)

در نمونه کد بالا شی myUser

 از نوع Lateinit تعریف شده است. درون بدنه تابع onCreate

 این شی ابتدا مقداردهی شده و سپس فراخوانی شده است. در نتیجه، خروجی حاصل از اجرای برنامه به صورت زیر خواهد بود.

MY NAME IS Hüseyin

در صورتی که این شی قبل از فراخوانی درون دستور println

 مقداردهی اولیه نشود، خطای زیر رخ خواهد داد.

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

برای جلوگیری از بروز خطا ناشی از دسترسی به شی مقداردهی نشده می‌توان از متد isInitialized()

 استفاده کرد. با استفاده از این متد می‌توان بررسی کرد که شی مورد نظر مقداردهی شده است یا خیر. در نمونه کد زیر این مسئله نشان داده شده است.

class MainActivity : AppCompatActivity() {
    //Here is the value we don't want to initialize at declaration time,
    //So we used the lateinit keyword.
    private lateinit var myUser: User

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        /**
        We can check if our value is initialize using isInitialized.
         */
        if (this::myUser.isInitialized) {
            println("MY NAME IS : " + myUser.name)
        } else {
            myUser = User("Hüseyin", "Özkoç")
            println("MY NAME IS : " + myUser.name)
        }
    }
}

data class User(var name: String, var surname: String)

با استفاده از ساختار شرطی if()

 به همراه متد isInitialized

 می‌توان از مقداردهی شدن شی مورد نظر اطمینان حاصل و از بروز خطا جلوگیری کرد.

تفاوت Lateinit با Lazy در کاتلین چیست؟

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

بر همین اساس در ادامه مهم‌ترین تفاوت‌های این دو روش تعریف متغیر آورده شده است.

استفاده از val و var

در هنگام استفاده از Lazy متغیر باید از نوع val و اصطلاحاً «غیرقابل تغییر» (Immutable) تعریف شود. در حالی که متغیرهای تعریف شده در زمان کاربرد Lateinit باید از نوع var باشند.

نحوه ذخیره‌سازی

روش Lateinit مقدار مورد نظر را درون فیلد پشتیبان ذخیره می‌کند. در نقطه مقابل، روش Lazy مقدار را درون شی کلاس ذخیره کرده و در صورت نیاز به آن ارجاع می‌دهد.

نحوه مقداردهی اولیه مشخصه‌ها به کمک Lazy در کاتلین

امکان اعمال مقادیر تهی

از Lateinit نمی‌توان برای مشخصه‌‌های «تهی‌پذیر» (nullable) یا نوع‌های داده «اولیه» (Primitive) استفاده کرد. مشخصه‌های تهی‌پذیر آن دسته از مشخصه‌هایی هستند که می‌توانند مقدار تهی داشته باشند. همچنین، منظور از نوع‌های داده اولیه مقادیری هستند که به صورت مستقیم درون متغیر ذخیره می‌شوند.

نحوه مقداردهی اولیه

امکان مقداردهی مشخصه‌های تعریف شده به کمک Lateinit در هر بخشی از کد وجود دارد. همچنین، مشخصه‌های Lateinit چندین مرتبه در طول اجرا می‌توانند مقداردهی شوند. در نقطه مقابل، مقداردهی مشخصه‌های تعریف شده به کمک Lazy تنها در زمان تعریف صورت می‌پذیرد و این مقدار قابل تغییر نیست.

امکان استفاده مجدد

امکان ذخیره‌سازی مقدار درون نمونه ساخته شده از نوع Lazy وجود دارد و می‌توان این نمونه را در هر بخشی از برنامه استفاده کرد. در نقطه مقابل، مشخصه تعریف شده از نوع Lateinit امکان ذخیره‌سازی مقدار ندارد.

پردازش همزمان چند رشته

مشخصه‌های تعریف شده از نوع Lateinit امکان پردازش همزمان چند رشته را ندارد. در نقطه مقابل، Lazy از چندین Thread به صورت همزمان پشتیبانی می‌کند.

پردازش همزمان چند کار توسط Lazy در کاتلین

بررسی مقداردهی شدن

برای بررسی مقداردهی شدن موجودیت ساخته شده از نوع Lazy می‌توان از متد isInitialized()

 استفاده کرد. همچنین، متد property::isInitialized()

 برای بررسی مقداردهی شدن مشخصه از نوع Lateinit مورد استفاده قرار می‌گیرد.

مزایا و معایب استفاده از Lazy در کاتلین

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

بر همین اساس، در این بخش از نوشته مزایا و معایب Lazy در کاتلین مورد بررسی قرار خواهد گرفت.

مزایای استفاده از Lazy در کاتلین چیست؟

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

کاهش استفاده از حافظه

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

بهبود عملکرد برنامه

یکی از مهم‌ترین مزایای استفاده از Lazy این است که با ایجاد تاخیر در مقداردهی اولیه اشیا زمان‌بر موجب بهبود عملکرد برنامه می‌شود. در واقع، مقداردهی این اشیا تنها در زمان نیاز صورت می‌گیرد. به عنوان مثال، استفاده از این قابلیت زمانی توصیه می‎‌شود که ایجاد شی در برنامه زمان‌بر است و حافظه زیادی را نیز درگیر می‌کند. ایجاد تاخیر در مقداردهی این اشیا ضمن بهبود عملکرد کلی برنامه موجب ذخیره‌سازی منابع نیز می‌شود.

بهبود خوانایی و درک آسان کد

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

چند برنامه‌نویس در حال بررسی کد نوشته شده با قابلیت Lazy در کاتلین

معایب استفاده از Lazy در کاتلین چیست؟

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

پیچیده شدن کد

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

سخت شدن فرایند تست کد

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

برنامه‌نویسان در حال اجرای فرایند تست بر روی کد کاتلین نوشته شده - Lazy در کاتلین

مدیریت دسترسی همزمان چند رشته

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

جمع‌بندی

زبان برنامه نویسی کاتلین در سال‌های اخیر توانسته با ارايه ویژگی‌های جذاب ضمن بهبود فرایند کد نویسی توجه توسعه‌دهندگان بسیاری را نیز به خود جلب کند. قابلیت Lazy در کاتلین نیز به عنوان یکی از ویژگی‌های جذاب این زبان برنامه نویسی شناخته می‌شود. استفاده از Lazy ایجاد و مقداردهی اولیه اشیا در برنامه را به تاخیر می‌اندازد. در نتیجه این امر عملکرد کلی برنامه نیز بهبود می‌یابد، زیرا ایجاد برخی از اشیا در زمان راه‌اندازی برنامه بخش زیادی از حافظه را درگیر می‌کند. به طور کلی، قابلیت Lazy ابزار مفیدی برای بهینه‌سازی عملکرد و استفاده صحیح از حافظه است و درک و خوانایی کد را نیز بهبود می‌بخشد.

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

source