کانال بله, جهت پشتیبانی و اطلاع رسانی کانال بله, جهت پشتیبانی و اطلاع رسانی
عضویت

استفاده از فریم ورک Dagger 2 جهت تزریق نیازمندی ها به پروژه (کلاس ها و کتابخانه های مورد نیاز)/ Dependency injection & Dagger 2

مقدمه ای بر مفهوم dependency injection >>استفاده از dependency injection در محیط و بستر اجرای Java

Dependency injection چیست؟

Dependency injection امروزه مفهومی است که در بسیاری از زبان های برنامه نویسی پیاده سازی می شود. مفهوم کلی تری که dependency injection بر آن پایه ریزی شده است، Inversion of Control می باشد. در مهندسی نرم‌افزار، وارونگی کنترل گونه‌ای از توسعه و طراحی اپلیکیشن را توصیف می‌کند که بخشهای اختصاصی نوشته شده یک برنامه رایانه‌ای جریان کنترل را از یک کتابخانه با قابلیت بازاستفاده عمومی دریافت می‌کند. معماری نرم‌افزاری ای که از این طرح به وجود می‌آید در مقایسه با برنامه نویسی سنتی رویه‌ای کنترل را وارونه می‌سازد: در برنامه نویسی سنتی کد اختصاصی که هدف برنامه را بیان می‌کند، برای مراقبت از وظایف عمومی کتابخانه‌های بازقابل استفاده را فرا می‌خواند اما با وراونه سازی کنترل این کدهای دارای قابلیت باز استفاده هستند که کدهای اختصاصی یا کدهای مربوط به وظیفه خاص را صدا می زنند. وارونگی کنترل برای افزایش ویژگی پودمانی/پیمانه ای (modularity) برنامه و قابلیت گسترش آن استفاده شده و در پارادایم برنامه نویسی شی گرا و دیگر الگوهای برنامه نویسی هم مورد استفاده قرار می گیرد.

Dependency Injection (فراهم نمودن نیازمندی های یک آبجکت) در برنامه‌نویسی شی گرا، الگوی توسعه است با قاعده ی اصلیِ جداکردنِ رفتار از تحلیلِ نیازمندی (Dependency Resolution) = روشی برای تجزیه‌کردنِ اجزا کاملا مستقلِ نرم‌افزاری. به صورت خلاصه Dependency injection، الگویی است جهت تزریق نیازمندی ها و آبجکت های مورد نیاز خارجی یک کلاس به آن، بجای استفاده مستقیم از آن‌ نیازمندی ها (فراهم کردن آبجکت مورد نظر) در داخل کلاس. با توجه به این مفهوم، یک کلاس نباید نیازمندی ها (dependency) خود را به صورت static تنظیم کند، بلکه می بایست آن را از بیرون و توسط کلاس های دیگر تامین نماید.

چنانچه یک کلاس جاوا از نمونه ی کلاس دیگری استفاده کند، گفته می شود که به آن کلاس وابستگی دارد. در اصطلاح این امر class dependency (وابستگی یک کلاس به کلاس خارجی) خوانده می شود. به عنوان مثال، کلاسی که به قابلیت های سرویس logger (ثبت گزارش) احتیاج داشته باشد، در اصطلاح گفته می شود که کلاس اول به کلاس سرویس دهی (service class) احتیاج/dependency دارد یا کلاس اول قابلیت ها و نیازمندی های خود را از کلاس خارجی تامین می کند.

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

چنانچه کلاس Java به واسطه ی کلیدواژه ی new، نمونه ای از کلاس دیگر را ایجاد کرده و مورد استفاده قرار دهد، در آن صورت نمونه ی ایجاد شده از کلاس قابل استفاده و تست به طور مجزا از کلاس میزبان نخواهد بود. از این رخداد تحت عنوان hard dependency (وابستگی شدید) یاد می شود.

package com.example.e4.rcp.todo.parts;
import java.util.logging.Logger;
public class MyClass {
        private final static Logger logger;
        public MyClass(Logger logger) {
                this.logger = logger;
                // write an info log message
                logger.info("This is a log message.")
        }
}
نکته :

لازم به توضیح است که کلاس حاضر، صرفا یک کلاس ساده ی جاوا بوده (هیچ ویژگی خاصی ندارد)، جز اینکه از ایجاد مسقیم آبجکت جلوگیری می کند.

کلاس framework که گاه dependency container نیز خوانده می شود، قادر است نیازمندهای این کلاس را به راحتی تحلیل و فراهم نماید (Dependency injection container یک کلاس است که می داند چگونه آّبجکت ها و متعاقبا آبجکت های وابسته به آن را نمونه سازی و تنظیم نماید). با این تجزیه و تحلیل، کلاس framework قادر خواهد بود یک نمونه از کلاس مورد نظر ایجاد کرده و سپس آبجکت های مورد نیاز را به واسطه ی java reflection، به داخل dependency های مشخص شده تزریق کند.

با این روش کلاس جاوا دیگر هیچ وابستگی شدید (hard dependency) ندارد، بدین معنی که دیگر برای تامین قابلیت ها و نیازمندی های خود به نمونه ای از کلاس دیگر احتیاج ندارد. این امکان به توسعه دهنده اجازه می دهد تا کلاس خود را، برای مثال با استفاده از آبجکت های ساختگی (mock)، به صورت ایزوله آزمایش نماید.

آّبجکت های ساختگی (mock) آبجکت هایی هستند که قابلیت و رفتار آبجکت واقعی را شبیه سازی می کنند و در واقع سعی دارند تا مانند آبجکت مورد نظر رفتار کنند. اما این آّبجکت های ساختگی یا mock برنامه ریزی نمی شوند بلکه طوری تنظیم شود که همیشه به یک شیوه ی از پیش تعیین شده و مشخص رفتار کنند. واژه ی mock یک کلمه ی انگلیسی است که معنی شبیه سازی و تقلید را در فارسی می دهد. بنابراین یک آبجکت mock، همان طور که قبلا نیز ذکر شد، رفتار یک آبجکت واقعی را شبیه سازی و تقلید می کند.

در صورت پیاده سازی الگو توسعه ی DI در پروژه، کلاس های جاوا به راحتی به صورت ایزوله قابل تست خواهد بود.

استفاده از annotation ها جهت توصیف و شرح نیازمندی ها / dependency های کلاس

روش های مختلفی برای شرح و مشخص کردن dependency های یک کلاس وجود دارد. پرکاربردترین روش معمولا در توصیف مسقیم dependency ها در کلاس میزبان با استفاده از annotation های Java خلاصه می شود (نشانه و علامت گذاری نیازمندها داخل کلاس با استفاده از annotation ها).

annotation های متعارف جاوا که برای شرح و نشانه گذاریdependency های یک کلاس مورد استفاده قرار می گیرند، همگی در JSR330 اعلان شده اند. JSR330 دو دستور @Inject و @Named را برای نشانه گذاری نیازمندی ها در کلاس، در اختیار توسعه دهنده قرار می دهد.

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

// import statements left out
public class MyPart {
        @Inject private Logger logger;
        // inject class for database access
        @Inject private DatabaseAccessClass dao;
        @Inject
        public void createControls(Composite parent) {
                logger.info("UI will start to build");
                Label label = new Label(parent, SWT.NONE);
                label.setText("Eclipse 4");
                Text text = new Text(parent, SWT.NONE);
                text.setText(dao.getNumber());
        }
}
توجه :

همان طور که در کد بالا مشاهده می کنید، کلاس حاضر از کلیدواژه یا عملگر new برای نمونه سازی و ساخت کامپوننت های رابط کاربری (UI) استفاده می کند. با این کار برنامه نویس اعلان می دارد که کامپوننت های رابط کاربری (اجزایی که با عملگر new نمونه سازی شده اند) قرار نیست در آینده تست شوند یا با آبجکت های ساختگی و شبیه ساز تست ها جایگزین گردند. به عبارت دیگر با پیاده سازی فوق، برنامه نویس تصمیم گرفته که به toolkit و مجموعه ابزار UI مربوطه وابستگی شدید (hard coupling) داشته باشد.

بر اساس JSR330، آّبجکت ها به کدام بخش از کلاس تزریق یا پاس داده می شوند؟

الگوی DI را می توان از روش های زیر در پروژه پیاده سازی کرد:

  • از طریق تابع سازنده ی (Constructor) کلاس که در اصطلاح construction injection خوانده می شود.
  • از طریق فیلد که در اصطلاح field injection نام دارد.
  • از طریق پارامترهای یک متد که از آن تحت عنوان method injection یاد می شود.

می توان از dependency injection برای فیلدها و متدهای static و غیر static نیز استفاده کرد. لازم به ذکر است که توسعه دهندگان خبره سعی می کنند از پیاده سازی DI بر روی متدها و فیلدهای static خودداری کنند چرا که علاوه بر به همراه داشتن محدودیت هایی، اشکال زدایی و فرایند debug را بسیار سخت می کند.

  • فیلدهای static پس از اینکه اولین آبجکت کلاس از طریق پیاده سازی DI ساخته شد، تزریق می شوند، بدین معنی که داخل تابع سازنده (constructor) دسترسی به آن ها امکان پذیر نخواهد بود.
  • فیلدهای static را نمی توان به عنوان final علامت گذاری کرد چرا که در این صورت، کامپایلر یا خود اپلیکیشن در زمان اجرای برنامه ایراد می گیرد.
  • متدهای static تنها یکبار پس از اینکه اولین نمونه از کلاس ایجاد شد، فراخوانی می شوند.

ترتیبی که DI بر اساس آن در کلاس اجرا و پیاده سازی می شود

بر اساس JSR330 الگوی DI به ترتیب زیر در کلاس پیاده سازی می شود:

  • تزریق نیازمندی ها از طریق تابع سازنده ی کلاس (Constructor injection).
  • تزریق از طریق فیلد کلاس (field injection).
  • تزریق با ارسال پارامتر به متد کلاس (method injection) .

JSR330، ترتیبی که متدها یا فیلدهای نشانه گذاری شده با @Inject بر آن اساس اجرا می شوند را تعیین نمی کند. به عبارت دیگر شما نمی توانید فرض را بر این بگذارید که متدها یا فیلدها به همان ترتیبی که در کلاس اعلان شده اند، فراخوانی می شوند.

توجه :

از آنجایی که فیلدها و پارامترهای متد پس از فراخوانی تابع سازنده (constructor) به داخل کلاس تزریق می شوند، امکان استفاده از متغیرهای عضو تزریق شده از بیرون در تابع سازنده وجود ندارد.

فریم ورک های جاوا و dependency injection

می توانید با تعبیه constructor ها و توابع setter/getter کافی داخل کلاس های پروژه ی خود، بدون استفاده از framework، الگو توسعه ی DI را پیاده سازی نمایید.

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

دو فریم ورک پرطرفدار جهت پیاده سازی DI عبارتند از Spring و Google Guice.

Dependency injection با فریم ورک Dagger2

Dagger2 چیست؟

همان طور که قبلا گفته شد، به این خاطر که ایزوله سازی آبجکت ها و فراهم کردن نیازمندی هایشان (dependency ها) به سادگی صورت پذیرد، می‌توان از فریمورک‌های مختلفی استفاده کرد که Dagger قطعا یکی از پرطرفدارترین و کاراترین آن ها است. Dagger یک فریم ورک جهت پیاده سازی الگو توسعه ی DI در پروژه محسوب می شود که مبتنی بر Java Specification Request (JSR) 330 طراحی شده، از ابزار code generation استفاده می کند و مبتنی بر annotation ها می باشد. کد تولید شده خوانا بوده و اشکال زدایی آن بسیار آسان می باشد.

Dagger2 از دو annotation زیر استفاده می کند:

  • @Module و @Provides: کلاس و متدهایی تعریف می کند که نیازمندی های (dependency) کلاس ها را در اختیارشان قرار می دهد.
  • @Inject: dependency هایی را فراخوانی یا درخواست می کند. می توان از این دستور برای نشانه گذاری Constructor، یک فیلد یا متد از کلاس استفاده کرد.
  • @Component: ماژول های انتخابی را فعال ساخته و برای پیاده سازی DI در پروژه استفاده می شود.

در فریم ورک Dagger اجازه ی استفاده از فیلدهایی که به صورت private تعریف شده اند برای field injection وجود ندارد چرا که Dagger 2 از generated code (و نه reflection) برای دسترسی به فیلدها استفاده می کند.

اعلان dependency provider ها (تعریف ارائه دهندگان آّبجکت های موردنیاز و نیازمندی های کلاس)

منظور از واژه ی dependency injection context، تعداد آبجکت هایی است که قابلیت تزریق و ارسال آن ها به عنوان پارامتر به کلاس ها وجود دارد.

در فریم ورک Dagger2، کلاس هایی که با دستور @Module نشانه گذاری شده اند، وظیفه ی ارائه ی آّبجکت هایی را دارند که قرار است به عنوان dependency و پارامتر در اختیار کلاس درخواست کننده قرار گیرد. این کلاس های میزبان می توانند در خود توابعی داشته باشند که با دستور @Proivdes نشانه گذاری شده و در خروجی آبجکت هایی را برمی گردانند که می توان از آن ها برای رفع نیازمندی های کلاس بهره گرفت.

متدهایی که با دستور @Provides نشانه گذاری شده باشند، این قابلیت را دارند که کلاس های مورد نیاز یا dependency ها را از طریق پارامترهای ارسالی به متد فراهم کنند. این آبجکت های مورد نیاز یا dependency ها را با فریم ورک Dagger2 در اختیار کلاس درخواست کننده قرار می دهیم.

تعریف آبجکت های مورد نیاز یا dependency ها (Object consumer)

جهت اعلان یک dependency کافی است ابتدا دستور @Inject را بالای کد مربوطه درج نمایید. پس از اینکه تابع سازنده (constructor) را با دستور مزبور نشانه گذاری کردید، Dagger2 قادر خواهد بود که با استفاده از نمونه ی این آبجکت (instance) نیازمندی ها یا به اصطلاح آبجکت های مورد نیاز کلاس را برآورده سازد. علت استفاده از روش مذکور برای پیاده سازی DI این است که از نوشتن چندین متد نشانه گذاری شده با @Provides و طولانی سازی کد خودداری شود.

برقراری ارتباط بین Consumer ها (کلاس های درخواست کننده یا مصرف کننده) و provider های (ارائه دهندگان) آبجکت های مورد نیاز

دستور @Component در سطح یک interface (الگوی پیاده سازی) مورد استفاده قرار می گیرد. فریم ورک Dagger2 با استفاده از این interface، کد لازم را تولید می نماید. ساختار نحوی برای پیاده سازی کلاس مورد نیاز (generated class) این است که Dagger به عنوان پیشوند درج شده و بلافاصله پس از آن اسم interface مربوطه قرار می گیرد. کلاسی که در نتیجه ی استفاده از الگوی فوق تولید می شود یک متد create در اختیار توسعه دهده قرار می دهد که به واسطه ی آن می توان آبجکت ها را بر اساس configuration ارائه شده، تنظیم کرد. متدهایی که در سطح interface اعلان شده اند می توانند به این آبجکت های تعریف شده دسترسی داشته باشند.

اینترفیس @Component ارتباط بین ارائه دهنده ی آبجکت ها و نیازمندی های کلاس (module ها) و آبجکت هایی که نیاز به dependency را اعلان می کنند، برقرار می نماید. جدول زیر توضیح مختصر و مفیدی پیرامون annotation های Dagger ارائه می نماید.

دستور/annotation
شرح کاربرد
@Module
در سطح کلاس هایی تعریف می شود که حامل متدهای نشانه گذاری شده با دستور @Provides می باشند.
@Provides
می تواند بر روی متدهای موجود در کلاس نشانه گذاری شده با دستور @Module مورد استفاده قرار گیرد. همچنین برای متدهایی کاربرد دارد که آبجکت های موردنیاز کلاس را برای پیاده سازی الگو توسعه ی DI در اختیار آن قرار می دهند.
@Singleton
با درج این دستور یک نمونه ی واحد از آبجکت ارائه شده، ساخته و به اشتراک گذاشته می شود.
@Component
در سطح یک interface مورد استفاده قرار می گیرد. این interface را فریم ورک Dagger2 برای تولید کدی که با استفاده از ماژول، نیازمندی ها و آبجکت های مورد درخواست کلاس را فراهم می کند، مورد استفاده قرار می گیرد.

Scope annotation

می توانید با استفاده از دستور @Singleton اعلان نمایید که از آبجکت مورد نظر یک نمونه بیشتر ایجاد نخواهد شد.

برخورد ویژه ی Dagger با فیلدها (field injection)

Dagger 2 خود به صورت اتوماتیک فیلدها را به داخل کلاس تزریق نمی کند. همچنین از تزریق فیلدهایی که با سطح دسترسی private تعریف شده اند، عاجز است. اگر می خواهید از روش field injection برای برطرف کردن نیازمندها استفاده نمایید، در آن صورت می بایست یک متد در سطح interface @Component خود اعلان نموده که نمونه ای که قرار است فیلد را به داخل آن تزریق کنید به عنوان پارامتر بپذیرد.

استفاده از Dagger 2 در محیط کاری Eclipse همراه با سیستم کامپایل Maven

جهت استفاده از محیط کاری Eclipse و سیستم کامپایل Maven با فریم ورک DI به نام e Dagger 2، لازم است ابزار Maven و افزونه ی apt را در محیط برنامه نویسی مزبور نصب کنید تا بدین وسیله Maven قابلیت تنظیم و آماده سازی پردازش گر annotation ها (annotation processer) را داشته باشد. برای فراهم آوردن قابلیت پشتیبانی Eclipse از سیستم Maven، بایستی به سایت آپدیت و بروز رسانی ویرایش مربوطه از Eclipse مراجعه نموده و پس از بارگیری m2e-apt از آدرس http://download.jboss.org/jbosstools/updates/m2e-extensions/m2e-apt آن را در محیط کاری مورد نظر نصب نمایید.

پس از نصب افزونه ی مربوطه، لازم است با طی کردن این مسیر داخل preferences (تنظیمات Eclipse): Window ▸ Preferences ▸ Maven ▸ Annotation Processing، پردازش مناسب (قابلیت پردازش annotation) را فعال سازی نمایید. حال گزینه ی Automatically configure JDT APT را انتخاب کنید.

Scope annotation تمرین کاربردی: پیاده سازی الگو توسعه ی DI از طریق فریم ورک Dagger2

در تمرین حاضر شما می بایست از فریم ورک Dagger 2 در یک برنامه ی متعارف جاوا که با سیستم کامپایل مدیریت Maven می شود، جهت پیاده سازی الگو توسعه ی DI استفاده نمایید.

این پروژه ی آزمایشی را می توانید در هر کدام از محیط های برنامه نویسی Java که مایلید پیاده سازی کنید. در تمرین جاری ما از محیط کاری Eclipse که ابزار Maven بر روی آن نصب شده، استفاده می کنیم. همچنین در صورتی که IDE نام برده را در اختیار ندارید، می توانید پروژه را از طریق یک text editor معمولی و با فراخوانی دستورات Maven در پنجره ی فرمان (command line) توسعه داده و کامپایل نمایید.

چنانچه قصد استفاده از محیط کاری Eclipse را دارید، در آن صورت لازم است ابزار لازم Maven را نیز دانلود و نصب نمایید.

ساخت پروژه ی آزمایشی و استفاده از فریم ورک Dagger 2

یک پروژه ی جدید Maven با اسم پکیج com.vogella.java.dagger2 ایجاد نمایید.

تعریف یا تنظیم و ویرایش فایل Maven build

محتوای فایل pom.xml را به صورت زیر ویرایش نمایید.


4.0.0
com.vogella.java.dagger2
com.vogella.java.dagger2
0.0.1-SNAPSHOT
com.vogella.java.dagger2


com.google.dagger
dagger
2.4
        

com.google.dagger
dagger-compiler
2.4
true
        
    



maven-compiler-plugin
3.3

1.8
1.8
                
            
        
    

تعریف و استفاده از کلاس هایی که به آبجکت هایی از بیرون نیاز دارند (درخواست dependency دارند)

دو کلاس زیر را تعریف کنید که برای عملکرد خود نیاز به آبجکت هایی از بیرون دارند (dependency هایی را از بیرون درخواست یا فراخوانی می کنند).

package com.vogella.java.dagger2;
import javax.inject.Inject;
public class User {
        private String firstName;
        private String lastName;
        public User(String firstName, String lastName) {
                this.firstName = firstName;
                this.lastName = lastName;
        }
        @Override
        public String toString() {
                return "User [firstName=" + firstName + ", lastName=" + lastName + "]";
        }
}
package com.vogella.java.dagger2;
import javax.inject.Inject;
public class BackendService {
        @Inject
        public User user;
        private String serverUrl;
        @Inject
        public BackendService(@Named("serverUrl") String serverUrl) {
                this.serverUrl = serverUrl;
        }
        public boolean callServer() {
                if (user !=null && serverUrl!=null && serverUrl.length()>0) {
                        System.out.println("User: " + user + " ServerUrl: "  + serverUrl);
                        return true;
                }
                return false;
        }
}

تعریف کامپوننت های مورد نیاز (دستور @Component)

این کامپوننت ها هستند که اعلان می کنند کدام module یا کامپوننت های نرم افزاری هستند که dependency ها و آبجکت های مورد نیاز را فراهم می کنند.Dagger 2 با استفاده از این interface (@Component) کد کلاس accessor را ایجاد می کند. این کلاس نیز متدهای آماده و تعریف شده در بدنه ی interface را برای پیاده سازی ارائه می دهد.

package com.vogella.java.dagger2.component;
import javax.inject.Singleton;
import com.vogella.java.dagger2.BackendService;
import com.vogella.java.dagger2.modules.BackEndServiceModule;
import com.vogella.java.dagger2.modules.UserModule;
import dagger.Component;
@Singleton
@Component(modules = { UserModule.class, BackEndServiceModule.class })
public interface MyComponent {
    // provide the dependency for dependent components
    // (not needed for single-component setups)
        BackendService provideBackendService();
        // allow to inject into our Main class
        // method name not important
        void inject(Main main);  			
} 

متدی که امکان تزریق و مقداردهی فیلدها در BackendServer را می دهد.

استفاده از کد تولید شده توسط Dagger

کلاس آزمایشی زیر به صورت کاربردی از کدی که فریم ورک Dagger به صورت آماده در اختیار آن قرار می دهد استفاده می نماید.

package com.vogella.java.dagger2.main;
import com.vogella.java.dagger2.BackendService;
import com.vogella.java.dagger2.component.DaggerMyComponent;
import com.vogella.java.dagger2.component.MyComponent;
public class Main {
    @Inject
    BackendService backendService; // 						
    private MyComponent component;
    private Main() {
        component = DaggerMyComponent.builder().build();
        component.inject(this);
    }
    private void callServer() {
        boolean callServer = backendService.callServer();
        if (callServer) {
            System.out.println("Server call was successful. ");
        } else {
            System.out.println("Server call failed. ");
        }
    }
        public static void main(String[] args) {
        Main main = new Main();
        main.callServer();
        }
}

از آنجایی که فیلدها به صورت اتوماتیک تزریق نمی شوند، متد مربوطه فراخونی شده و فیلدهای مورد نظر را مقداردهی می نماید.

Dagger 2 و اندروید

پیاده سازی الگو توسعه ی DI در پروژه های اندرویدی

بسیاری از کامپوننت های نرم افزاری اندروید همچون activity ها را خود فریم ورک اندروید نمونه سازی می کند. به عبارتی دیگر کار توسط توسعه دهنده به صورت دستی انجام نمی گیرد. این امر فراهم کردن آبجکت های مورد نیاز کلاس (dependency) و کامپوننت های نرم افزاری اندروید از بیرون به وسیله ی توابع سازنده (constrcutor) را پیچیده و دشوار می کند.

استفاده ی کاربردی از Dagger 2 در اندروید

جهت فعال سازی Dagger 2و امکان استفاده از آن در پروژه، لازم است فایل build.gradle را ویرایش نمایید.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.0-alpha3'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}
allprojects {
    repositories {
        jcenter()
    }
}
task clean(type: Delete) {
    delete rootProject.buildDir
}

المان های زیر را به فایل app/build.gradle اضافه نمایید.

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
android {
    compileSdkVersion 22
    buildToolsVersion "23.0.0"
    defaultConfig {
        applicationId "com.vogella.android.dagger2simple"
        minSdkVersion 22
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
ext {
    JUNIT_VERSION = '4.12'
    DAGGER_VERSION ='2.4'
}
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.google.dagger:dagger:2.4'
    apt "com.google.dagger:dagger-compiler:DAGGER_VERSION"
    provided 'javax.annotation:jsr250-api:1.0'
    compile 'javax.inject:javax.inject:1'
    testCompile "junit:junit:$JUNIT_VERSION"
    testApt "com.google.dagger:dagger-compiler:DAGGER_VERSION"
}
نکته :

لازم به ذکر است که بایستی apt را برای هر scope ای که Dagger قرار است در آن مورد استفاده قرار بگیرد، دقیق تنظیم و مشخص نمایید. به طور مثال، برای تست اپلیکیشن های معمولی apt، برای تست هایی که بر روی دستگاه مجازی جاوا/JVM اجرا می شودtestApt و در نهایت برای تست های اندرویدی نیز از testAndroidApt استفاده نمایید.

تمرین کاربردی: پیاده سازی الگو توسعه ی DI با استفاده از فریم ورک Dagger 2 در پروژه های اندرویدی

هدف از تمرین

در این تمرین، Dagger 2 به صورت عملی در یک پروژه ی اندرویدی جهت پیاده سازی DI مورد استفاده قرار می گیرد. در برنامه ی ساده ی بخش حاضر، یک کلاس activity تعریف می شود که قابلیت احراز هویت (authenticate) و بررسی صحت گذرواژه/نام کاربری (credentials) را به کاربر می دهد. مقدار خروجی و نتیجه ی پیاده سازی در کادر متن (text field) به نمایش گذاشته می شود.

ساخت پروژه

یک پروژه ی جدید با اسم پکیج (top level package) com.vogella.android.dagger2simple ایجاد نمایید.

محتوای فایل layout ای که ظاهر کامپوننت activity و صفحه ی قابل مشاهده برای کاربر را مشخص می کند، به صورت زیر ویرایش نمایید.




افزودن dependency ها و کتابخانه های مورد نیاز سیستم کامپایل Gradle

آیتم های زیر را به فایل build.gradle پروژه اضافه نمایید.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.0-alpha3'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}
allprojects {
    repositories {
        jcenter()
    }
}
task clean(type: Delete) {
    delete rootProject.buildDir
}

حال المان های زیر را به فایل app/build.gradle اضافه نمایید.

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
android {
    compileSdkVersion 22
    buildToolsVersion "23.0.0"
    defaultConfig {
        applicationId "com.vogella.android.dagger2simple"
        minSdkVersion 22
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
ext {
    JUNIT_VERSION = '4.12'
    DAGGER_VERSION ='2.4'
}
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.google.dagger:dagger:2.4'
    apt "com.google.dagger:dagger-compiler:DAGGER_VERSION"
    provided 'javax.annotation:jsr250-api:1.0'
    compile 'javax.inject:javax.inject:1'
    testCompile "junit:junit:$JUNIT_VERSION"
    testApt "com.google.dagger:dagger-compiler:DAGGER_VERSION"
}

ترسیم یا اعلان نمودار dependency ها و نمایش وابستگی آبجکت های مورد نیاز به یکدیگر

کلاس های زیر را جهت ارائه کردن dependency ها و متصل کردن آن ها به یکدیگر (آّبجکت های مورد نیاز جهت رفع نیازمندی ها) در پروژه ایجاد نمایید.

package com.vogella.android.dagger2simple;
public class NetworkApi {
    public boolean validateUser(String username, String password) {
        // imagine an actual network call here
        // for demo purpose return false in "real" life
        if (username == null || username.length() == 0) {
            return false;
        } else {
            return true;
        }
    }
}
package com.vogella.android.dagger2simple.modules;
import com.vogella.android.dagger2simple.NetworkApi;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class NetworkApiModule {
    @Provides
    @Singleton
    public NetworkApi getNetwork(){
        return new NetworkApi();
    }
}
package com.vogella.android.dagger2simple.components;
import android.app.Activity;
import com.vogella.android.dagger2simple.MainActivity;
import com.vogella.android.dagger2simple.NetworkApi;
import com.vogella.android.dagger2simple.modules.NetworkApiModule;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {NetworkApiModule.class})
public interface DiComponent {
    // to update the fields in your activities
    void inject(MainActivity activity);
}

ساخت آبجکت Application پروژه (Wiring up the application)

در بدنه ی کلاس Application پروژه ی خود، کد زیر را پیاده سازی کنید. این کلاس بستر (context) پیاده سازی DI را فراهم کرده و سپس با فراخوانی تابع getComponent دسترسی به آن را مهیا می کند.

package com.vogella.android.dagger2simple;
import android.app.Application;
import com.vogella.android.dagger2simple.components.DaggerDiComponent;
import com.vogella.android.dagger2simple.components.DiComponent;
public class MyApplication extends Application {
    DiComponent component;
    @Override
    public void onCreate() {
        super.onCreate();
        component = DaggerDiComponent.builder().build();
    }
    public DiComponent getComponent() {
        return component;
    }
}

البته لازم است این کلاس را در لایه ی XML (داخل فایل تنظیمات اپلیکیشن Manifest) نیز ثبت و معرفی نمایید.








            
        
    

activity فعلی مقادیر تولید شده را از طریق متدی که در سطح کلاس @Component تعریف شده، تزریق می کند.

package com.vogella.android.dagger2simple;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;
import javax.inject.Inject;
public class MainActivity extends Activity {
    @Inject NetworkApi networkApi;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ((MyApplication) getApplication()).getComponent().inject(this);
        boolean injected =  networkApi == null ? false : true;
        TextView userAvailable = (TextView) findViewById(R.id.target);
        userAvailable.setText("Dependency injection worked: " + String.valueOf(injected));
    }
}

تست اپلیکیشن و کسب اطمینان از عملکرد صحیح اپلیکیشن

اپلیکیشن را اجرا نمایید. UI اپلیکیشن می بایست صریحا اعلان کند که dependency injection با موفقیت انجام شد.در غیر این صورت، نگاهی به کلاس های تولید شده در پوشه ی app/build/generated/apt بیاندازید. این کلاس ها بایستی به طور خوانا سازماندهی شده و دقیقا مشخص کنند که چه خطایی رخ داده است.

کسب اطمینان از عملکرد صحیح اپلیکیشن

جایگزین کردن Module@ با کلاس های اختصاصی خود در تست

می توانید در تست های نرم افزاری بجای کلاس های Dagger کلاس های اختصاصی خود را قرار دهید و این فریم ورک را طوری تنظیم کنید که از این کلاس ها استفاده کند. builder فریم ورک Dagger توابعی دارد که ماژول مورد نظر را ساخته و مقدار دهی می کند. این امکان برای شما وجود دارد که با استفاده از builder فریم ورک مزبور کلاس های Dagger را با کلاس دلخواه خود (کلاسی که باید در تست مورد استفاده قرار گیرد) جایگزین نمایید.

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

public class ImportantClass {
    private MyRestService restService;
    private MyLogger logger;
    @Inject public MainService(MyRestService restService, MyLogger logger) {
        this.restService = restService;
        this.logger = logger;
    }
    public void perform() {
        String s = restService.getData();
        logger.write(s.toUpperCase());
    }
}

می توانید این کلاس را با Mockito تست نمایید.

public class ImportantClassTest {
public class MainServiceTest {
    @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
    @Mock MyRestService restService;
    @Mock MyLogger logger;
    @InjectMocks ImportantClass importClassToBeTested;
    @Test
    public void performShouldWriteOutput() {
        when(restService.getData()).thenReturn("abc");
        importClassToBeTested.perform();
        verify(logger).print("ABC");
    }
}
}
1396/01/18 6589 2143
رمز عبور : tahlildadeh.com یا www.tahlildadeh.com
نظرات شما

نظرات خود را ثبت کنید...