Google
خانه / آموزش / کار با Dagger2 به زبان ساده – بخش اول

کار با Dagger2 به زبان ساده – بخش اول

چقدر خوب می شد اگر هر وقت نیازمندی داشتیم فقط آن را مطرح می کردیم و به سادگی برایمان فراهم می شد؟ مفهوم Dependency Injection یا به اختصار DI همین است، شما فقط می گویید من آن object را می خواهم و شخص دیگری آن را با تمام پیچیدگی هایش برایتان فراهم می کند! در ضمن تمام امور مربوط به تولید، نگهداری، چرخه حیات و مسائل مربوط آن شئی را برایمان کنترل می کند و باعث می شود ارتباطات بین کلاس ها ساده تر شود و در نتیجه نگهداری و توسعه ساده تر گردد. قرار است این کار را توسط Dagger2 در اندروید پیاده سازی نماییم.

Dependency Injection is a set of software design principles and patterns that enable us to develop loosely coupled code.

Loose coupling makes code extensible, and extensibility makes it maintainable

Mark Seemann

فرض بر این است که با مفهوم DI به عنوان یکی از الگو های بسیار مهم مهندسی نرم افزار آشنا هستید. در صورتی که آشنایی ندارید حتماً راجع به آن مطالعه فرمایید. شما می توانید کتاب Mark seemann را از اینجا دانلود کنید. در ضمن دوست و همکار عزیزم مثالی خوب و  ملموسی با نام “Dependency Injection in the Coffeeshop” دارد که دیدن آن خالی از لطف نیست.

  Dagger2 یک Dependency Injection FrameWork است که از تولید خودکار کد بر پایه annotations بهره می گیرد. (در مورد انوتیشن می توانید به این آموزش مراجعه کنید) یعنی برای استفاده از امکانات آن باید از annotation های خاصی استفاده کنیم که به توضیح آنها خواهم پرداخت. بخاطر اینکه  کار با Dagger2 کمی ابهام دارد و فهم آن برای خودم هم کمی طول کشید، مثال ساده ای آماده کردم که نحوه کار با Dagger2 را خیلی واضح و راحت باهم برسی کنیم. فقط توجه داشته باشید که هدف اصلی این مقاله نحوه کارکرد با Dagger2 است و نه پیاده سازی صددرصد صحیح Dependency Injection و تلاش می کنیم در مقاله دوم به این امر نیز پرداخته و پروژه را از هر نظر باهم تکمیل کنیم.

به نظرم یک از دلایل پیچیدگی های کار با Dagger2 ، روش های گوناگون پیاده سازی آن است که شاید بعضاً کار کند ولی از نظر DI درست نباشد و می توان با اطلاع  از این روش ها تسلط خوبی برآن پیدا کرد. بنابر این سعی می کنم به مرور کد را تمیز تر و به DI اصولی نزدیک تر کنم. پس تا آخر این مقاله با من باشید و درصورت وجود هرگونه ابهام یا ایراد یا سوال آن را با من در میان بگذارید تا در اسرع وقت پاسخ دهم.

قدم اول: اضافه کردن Dagger2 به پروژه

اول از همه اطلاعات زیر را به Gradle سطح پروژه اضافه کنید:

سپس خط زیر را در آخر Gradle سطح برنامه اضافه کنید.

و وابستگی های زیر را نیز به همان فایل اضافه نمایید

سپس Gradle خود را sync کنید.

به روز رسانی: اخیراً به علت کاربرد زیاد Dagger گوگل امکان اضافه کردن این کتابخانه را بصورت مستقیم فراهم کرده و می توان بدین صورت نیز عمل کرد:

 

قدم دوم: آماده سازی کلاس هایی که می خواهیم برایمان تزریق شوند و ارتباط بین آنها (Wiring up)

مفاهیم اصلی مطرح در Dagger2 به شرح زیر می باشند که همانطور که اشاره کردم برای استفاده از این مفاهیم باید از انوتیشن های هم نام آنها استفاده کرد.

Provides@

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

Module@

کلاسی هست که Provider ها در آن قرار می گیرند.

Component@

اینترفیسی که ارتباط وابستگی ها و استفاده کننده آن را مطرح می کند. ما فقط اینترفیس را می نویسیم و پیاده سازی آن با خود Dagger است.

Inject@

هرکجا که نیاز به تزریق وابستگی داشتیم از این انوتیشن استفاده می کنیم.

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

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

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

پس ما تا اینجا نحوه وابستگی کلاس ها را با انوتیشن Inject@ مشخص کردیم. یعنی به Dagger گفتیم که هنگام ساخت موتور رادیاتور و کامپیوتر را در اختیارم قرار بده! ( حالا نمی دانم چطوری، هرطور خودت صلاح می دونی!) ولی قبل آن لازم است تا یکبار نحوه ساخته شدن وابستگی ها را به Dagger را معرفی کنیم. برای اینکار کلاسی با نام ماژول می سازیم که از انوتیشن Module@ استفاده می کند و بخش های آن که نحوه ساختن وابستگی را شرح می دهند با انوتیشن Provides@ مشخص می کنیم.

اگر قرار است وابستگی های ما ورودی داشته باشند آن را در Constructor مربوط به Module پاس می دهیم. همانطور که می بینید اسم رادیاتور، اسم کامپیوتر و ولتاژ آن ( که جزو ورودی های ما بودند) به همراه Constructor پاس داده شد. ما در Module به Dagger می فهمانیم که وقتی آبجکتی خواستیم آن را چگونه تولید کند. در ضمن با استفاده  ازاسکوپ Singletone@ به Dagger می فهمانیم که همیشه یک نمونه از آن داشته باشد و هر دفعه یک نمونه جدید تولید نکند. برای مشاهده نحوه کار Scope به این مقاله مراجعه کنید.

کلاسی بعدی که به آن نیاز داریم interface ای هست که Dagger توسط آن می فهمد که کدام ماژول ها را در کجا باید تزریق کند و برای تولید کد خودکار Dagger مورد نیاز است، ولی اجازه بدهید تا این کلاس را بعد از نحوه استفاده از Dagger شرح دهم. فقط همین اندازه بدانیم که دو متد با نام دلخواه ( من نام هردو را void inject گذاشتم.) یکی با ورودی MainActivity و دیگری Motor ساختم.

خیلی خوب. حال همه چیز آماده است و نوبت به این می رسد که  object مورد نظرمان یعنی موتور را در کلاس تزریق و استفاده کنیم. فقط یک نکته باقی مانده است. لازم است تا Dagger کد خودکار را تولید کند و ورودی ماژول را به آن بدهیم تا کل ارتباطات کامل و تمامی آبجکت ها قابل تزریق باشد. اصطلاحاً سیم کشی یا Wiring up را تمام کنیم.  فقط توجه نمایید که برای اینکه dagger بتواند کدهای مربوطه خود را auto generate کند بعد از این بخش ( یعنی ساخت ماژول ها و کامپوننت) باید کل پروژه را Rebuild کنیم. به متد generateDaggerCode توجه نمایید:

حال به تجزیه تحلیل کد دقت نمایید

اول از همه کد Dagger را تولید می کنیم که در ورودی آن ماژول را با ورودی آن پاس می دهیم. ( نام رادیاتور، نام کامپیوتر، ولتاژ) سپس دو متد موجود در Component را صدا می زنیم. حال ما هرجای این کد که یک موتور خواستیم کافی است آن را با Inject@ بیاوریم و خود Dagger آن را مدیریت می کند.

چند نکته و قانون وجود دارد که فهم آنها باعث می شود این پیچیدگی کم شود. اول اینکه اگر هرجایی Inject را در فیلد های یک کلاس انجام دادیم ( مثل همین جا که در MainActivity کلاس Motor را در اول کلاس تزریق کرده و سپس از آن استفاده کردیم) Dagger متوجه آن نمی شود مگر اینکه  یک متدی ( با نام دلخواه) در Component بنویسم که ورودی آن، همان کلاس استفاده کننده باشد تا Dagger بفهمد چه کلاسی در فیلد خود Inject انجام داده و بتواند کد خود را بصورت صحیح تولید کند. برای همین امر بود که در Component یک متد به نام inject ساختم که ورودی آن MainActivity بود. همانطور که ملاحظه می کنید چون خود Motor هم در داخل خودش field Injection داشت ( هم Computer و هم Radiator در خط ۴ و ۷ کلاس موتور) پس ناچاراً متدی هم برای آن در Component نوشتم تا ارتباط آنها را نیز متوجه بشود و به اصطلاح فیلداینجکشن ها را اسکن کند.

اگر برنامه را اجرا کنید Motor به درستی Inject شده و پیغام مناسب دریافت خواهید کرد و ما توانسته ایم با استفاده از Dagger2 وابستگی را به کد تزریق کنیم.

حال توجه شما را به یک پیاده سازی دیگر جلب می نمایم. در این روش برای ساخت موتور، اجزای لازم را بدون استفاده از انوتیشن Inject@ و با Constructor پاس می دهم. همانطور که در بالا گفتیم چون تزریق در فیلد انجام ندادیم پس در کامپوننت هم متد آن ( void inject(Motor moto)) را حذف می کنیم. بدین صورت:

پس Dagger می فهمد که برای ساخت موتور باید دو ورودی رادیاتور و کامپیوتر به موتور پاس دهد، از طرفی نحوه ساخت کامپیوتر و رادیاتور را می داند. پس همین کافی است که بتواند موتور را برایمان تولید کند و Component هم بصورت زیر می شود:

حال اگر از شما بپرسند چرا متدی قرار دادیم با نام inject که ورودی آن از جنس MainActivity است جواب دهید: بخاطر اینکه در MainActivity در فیلد Motor را تزریق کرده ایم! بله درست گفتید!

همانطور که ملاحظه کردید هرجایی که بخواهیم وابستگی را تزریق کنیم باید  از Inject@ استفاده کنیم و بنابراین باید کلاسی که در آن تزریق کرده ایم را توسط متدی در Component به Dagger معرفی کنیم. به نظر می رسد که بهتر است در هنگام ایجاد و تعریف وابستگی ها (Register) تا حد امکان از Inject@ استفاده نکنیم و در صورت امکان بصورت ورودی به Constructor بدهیم. دلیل من هم این است که هم خوانایی کد بالا می رود و هم تعداد متدهای Component کمتر می شود و در هنگام Resolve از Inject@ استفاده کنیم. ( Register, Resolve, Release پترنی است که در DI Container مورد استفاده قرار می گیرد که در مقاله بعد بیشتر راجع به آن صحبت خواهم کرد. )

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

github_download

۱۳ دیدگاه

  1. private void generateDaggerCode() {
    DaggerMotorComponent.builder().motorModule(new MotorModule
    (this, “Computer name”,12, “radiator name”)).build().inject(this);

    }
    ممنون از مقاله بسیار مفیدتون
    فقط ی سوال اینکه من اینجا دگرموتورکامپوننت رو نتونستم بفهمم از کجا میاد چون برا من خطا داره این قسمت…کجا باید ساخت و محتواش چیه؟

    • خواهش می کنم. ببینید خود dagger در کد generate شده اش کامپوننت رو بر می گردونه که ما می تونیم یا بگیرمش و در یک متغیر بگذاریمش یلا اینکه inline از component استقاده کنیم و متدهاشو صدا بزنیم. توجه کنید DaggerMotorComponent کد تولید شده خود dagger است که با پاس دادن module هایش کامپوننت مورد نظر ما رو تولید می کنه. اگر دقت کنی می بینی که در تعریف کلاس کامپوننت در بالای کلاس modules = {MotorModule.class} را به عنوان ماژول پاس دادیم. پس وقتی می خواهیم در کد generate شده dagger (یعنی DaggerMotorComponent.builder() ) استفاده کنیم باید ماژول هایی راکه برایش تعریف کرده بودیم و پاس بدیم و در نهایت متد موجود در component رو صدا بزنیم که یکیش void inject(MainActivity activty) و دیگریvoid inject(Motor moto); بوده که در خط بعدی صدا زده شده.

      • سلام
        ممنونم از وقتی که میزارین
        متاسفانه دوباره متوجه نشدم
        اگر امکانش هست کد این برنامه تون رو بدید من ببینم اون خط چطوری باید پیاده بشه
        ممنونم
        telegram : smo2020

      • قهمیدم ممنونم
        فقط کاش قبلا از سیمکشی میگفتید پروژه رو کامپایل کنید تا دگر قابل استفاده مارو بسازه
        ممنونم در کل…ببخشید اولین بارمه دارم از دگر استفاده میکنم زیاد آشنا نبودم وتازه کارم

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

    • خواهش می کنم. اتفاقا راجع به این موضوع هرچی بیشتر صحبت و بحث بشه بهتره و بهتر جا میافته. خوشحال می شم اگر سوالی داشتی مطرح کنی.

  3. مرسی از مقالتون

  4. مطالبتون رو دنبال میکنم عموما از اینکه کاملا کاربردی توضیح میدید بسیار لذت میبرم و نهایت تشکر رو دارم.

دیدگاهتان را ثبت کنید

آدرس ایمیل شما منتشر نخواهد شدعلامتدارها لازمند *

*

bigtheme