Dagger 2 در اندروید (بخش دوم) — راهنمای پیشرفته

۸۴ بازدید
آخرین به‌روزرسانی: ۲۷ شهریور ۱۴۰۲
زمان مطالعه: ۵ دقیقه
Dagger 2 در اندروید (بخش دوم) — راهنمای پیشرفته

اگر از موضوعات منفی که پیرامون Dagger همواره مطرح بوده‌اند، تأثیر نپذیرفته‌اید و می‌خواهید از آن استفاده کنید؛ احتمالاً به عملکرد آن اهمیت می‌دهید و می‌خواهید بیشترین بازدهی را از آن بگیرید. در این صورت توصیه می‌کنیم این مقاله را تا به انتها مطالعه کنید.

این مقاله در واقع بخش دوم «Dagger 2 در اندروید (بخش اول) — راهنمای پیشرفته» است. هدف ما در این مقاله دیگر این نیست که دگر را ساده‌تر کنیم. در این نوشته می‌خواهیم مطمئن شویم که از دگر به همان ترتیبی که انتظار می‌رود استفاده می‌کنیم. برای نمونه این وضعیت در اغلب موارد منتهی به بهبود عملکرد می‌شود. نکته خوب این است که مواردی که در این راهنما ارائه می‌شوند کاملاً ساده هستند و جای هیچ نگرانی نیست.

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

  • از حیطه‌بندی غیرضروری اجتناب کرده و به وهله‌های Reusable@ اهمیت بدهید.
  • همواره تلاش کنید از متدهای استاتیک استفاده کنید.
  • چارچوب اپلیکیشن را به جای ماژولی با «آرگومان سازنده» (constructor argument)‌؛ از طریق «کامپوننت ساز» (‌component builder) ‌عرضه کنید.
  • در تست‌ها ‌وابستگی‌ها را به جای اُورراید کردن (Overriding) ماژول‌ها؛ ‌از طریق کامپوننت تست سوئیچ کنید.
  • از دگر در تست‌های واحد کلاس مفرد استفاده نکنید، چون به آن نیازی ندارید.

حیطه‌بندی (Scoping)

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

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

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

استاتیک

این یکی از ساده‌ترین و جالب‌ترین راهنمایی‌ها محسوب می‌شود. شما باید اطمینان حاصل کنید، متدهایی که ارائه می‌کنید استاتیک هستند. کل ایده به همین سادگی است.

هر ماژولی که متدهای Provides@ آن همگی استاتیک باشند، در زمان پیاده‌سازی به هیچ نوع وهله‌ای نیاز نخواهد داشت.

اگر ماژول شما صرفاً متدهای provide استاتیک داشته باشد، دگر هرگز نیازی به وهله‌سازی از آن نخواهد داشت و اگر نتوانید به دلیل حالت دار بودن ماژول این کار را بکنید، باید این گزینه را یک بار دیگر بررسی کنید.

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

این راهنما احتمالاً یکی از بدیهی‌ترین مواردی است که در این نوشته مطرح کرده‌ایم؛ اما این موضوع در دنیای کاتلین کمی پیچیده‌تر خواهد شد، چون در آنجا نمی‌توانید به سادگی مانند جاوا، یک متد استاتیک در یک کلاس داشته باشید. شما در کاتلین اساساً دو گزینه در اختیار دارید، می‌توانید از یک شیء companion استفاده کنید و یا این که ماژول خود را به یک object تبدیل کرده و متدهای provide را با JvmStatic@ حاشیه‌نویسی (annotate) کنید.

1@Module
2object YourModule {
3
4    @Provides @JvmStatic
5    fun provideThatDependency() = ThatDependency()
6}

خبر خوب این است که در صورت استفاده از R8 می‌توانید عملاً JvmStatic@ را کنار بگذارید، چون با استفاده از استاتیک سازی خود این وظیفه را از عهده شما بر می‌دارد.

چارچوب اپلیکیشن

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

برای حل این مشکل، معمولاً این کد استفاده می‌شود:

1@Singleton
2@Component(modules = [ApplicationModule::class, YourModule::class, ThatOtherModule::class])
3interface ApplicationComponent
4
5@Module
6class ApplicationModule(private val applicationContext: Context) {
7
8    @Provides fun provideApplicationContext(): Context = applicationContext
9}
10
11class YourApplication : Application() {
12
13    val component: ApplicationComponent by lazy {
14        DaggerApplicationComponent.builder()
15            .applicationModule(ApplicationModule(applicationContext))
16            .build()
17    }
18}

در کد فوق یک ماژول ایجاد می‌شود که چارچوب اپلیکیشن را به عنوان یک آرگومان سازنده دریافت می‌کند و یک متد provide ایجاد می‌کنیم که آن را عرضه کند. این وضعیت به خوبی عمل می‌کند؛ اما در ادامه دیگر نمی‌توانیم ماژول را به عنوان یک object در اختیار داشته باشیم، چون یک object نمی‌تواند پارامترهای سازنده داشته باشد. بنابراین اگر بخواهیم متدهای provide را به صورت استاتیک داشته باشیم، باید شیء companion را به صورت annotated اضافه کنیم که حالت زشتی دارد. اما علاوه بر آن، این راهبرد عملاً بر خلاف مستندات است که در این موارد به صراحت اعلام کرده است:

متدهای BindsInstance@ برای نوشتن یک Module@ باید بر آرگومان‌های سازنده ترجیح داده شوند و بلافاصله آن مقادیر ارائه شود. در واقع شما باید حالت زیر را داشته باشید:

1@Singleton
2@Component(modules = [YourModule::class, ThatOtherModule::class])
3interface ApplicationComponent {
4
5    @Component.Builder
6    interface Builder {
7        @BindsInstance fun applicationContext(applicationContext: Context): Builder
8        fun build(): ApplicationComponent
9    }
10}
11
12class YourApplication : Application() {
13
14    val component: ApplicationComponent by lazy {
15        DaggerApplicationComponent.builder()
16            .applicationContext(applicationContext)
17            .build()
18    }
19}

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

تست کردن

دلایل مختلفی برای سوئیچ کردن وابستگی‌ها در هنگام اجرای تست‌ها وجود دارند. به طور کلی یا دست کم زمانی که هدف ما اجرای تست نهایی نباشد، بهتر است تست‌ها را از دنیای واقعی جدا کنیم. یکی از بهترین روش‌ها نیز این است که تست‌های اسپرسو را به تقلید از یک (API MockWebServer و RESTMock FTW) از طریق داکر اجرا کنید.

این که همه تست‌ها از دنیای خارج جداسازی شده باشند، بسیار حائز اهمیت است.

شگفت‌انگیز است که مستندات دگر در مورد تست کردن کاملاً خوب هستند. روش‌های زیادی برای سوئیچ کردن وابستگی‌های دگر در زمان تست کردن وجود دارند. می‌توان ماژول‌ها را اُوِراید کرد و این همان کاری است که DaggerMock عملاً در پشت صحنه انجام می‌دهد. اما علی‌رغم این که این راهبرد در ابتدا جالب به نظر می‌رسد؛ اما مستندات صراحتاً توصیه کرده‌اند که از آن اجتناب کنید، زیرا محدودیت‌هایی با خود به همراه دارد. ضمناً از آنجا که ماژول‌های استاتیک نمی‌توانند اُوراید شوند، در این روش نمی‌توانیم متدهای provide استاتیک داشته باشیم.

روشی که باید انتخاب کنیم این است که کل کامپوننت را با استفاده از TestComponent برای تست‌ها اُوراید کنیم. اما برای این که این راهبرد موفق باشد، باید مطمئن شویم که ماژول‌ها به طور صحیحی سازمان یافته‌اند. هدف ما این نیست که مستندات دگر را تکرار کنیم و از این رو اعلام می‌کنیم که مستندات دگر در این موارد کاملاً محق هستند و بهتر است بر اساس توصیه‌های آن پیش بروید. همچنین بخش آخر مستندات دگر را در مورد عدم استفاده از دگر روی تست‌های واحد (Unit Test) تک کلاسی را نیز مطالعه کنید.

سخن پایانی

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

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۱ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
proandroiddev
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *