مبانی Dagger

تزریق وابستگی دستی یا مکان‌یاب‌های سرویس در برنامه Android می‌تواند بسته به اندازه پروژه‌تان مشکل‌ساز باشد. بااستفاده از Dagger برای مدیریت وابستگی‌ها، می‌توانید پیچیدگی پروژه‌تان را هنگام مقیاس‌بندی محدود کنید.

Dagger به‌طور خودکار کدی را تولید می‌کند که از کدی که درغیراین‌صورت باید به‌صورت دستی می‌نوشتید تقلید می‌کند. چون کد در زمان ترجمه تولید می‌شود، قابل‌ردیابی است و عملکرد بهتری نسبت به سایر راه‌حل‌های مبتنی بر بازتاب مانند Guice دارد.

مزایای استفاده از Dagger

Dagger شما را از نوشتن کد متن استاندارد خسته‌کننده و مستعد خطا با انجام کارهای زیر رها می‌کند:

  • تولید کد AppContainer (نمودار برنامه) که به‌صورت دستی در بخش DI دستی پیاده‌سازی کرده‌اید.

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

  • تصمیم‌گیری درباره اینکه آیا از وابستگی مجدداً استفاده شود یا نمونه جدیدی ازطریق استفاده از دامنه‌ها ایجاد شود.

  • ایجاد محفظه برای جریان‌های خاص، همان‌طور که برای جریان ورود به سیستم در بخش قبلی بااستفاده از زیرمؤلفه‌های Dagger انجام دادید. این کار با آزاد کردن اشیاء در حافظه زمانی که دیگر به آن‌ها نیازی نیست، عملکرد برنامه‌تان را بهبود می‌بخشد.

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

در زمان ساخت، Dagger کد شما را بررسی می‌کند و:

  • نمودارهای وابستگی را می‌سازد و اعتبارسنجی می‌کند و مطمئن می‌شود که:

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

یک مورد استفاده ساده در Dagger: تولید یک کارخانه

برای نشان دادن نحوه کار با Dagger، بیایید یک کارخانه ساده برای کلاس UserRepository نشان‌داده‌شده در نمودار زیر ایجاد کنیم:

UserRepository را به‌صورت زیر تعریف کنید:

کاتلین

class UserRepository(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

جاوا

public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }

    ...
}

حاشیه‌نویسی @Inject را به سازنده UserRepository اضافه کنید تا Dagger بداند چگونه UserRepository ایجاد کند:

کاتلین

// @Inject lets Dagger know how to create instances of this object
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

جاوا

public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    // @Inject lets Dagger know how to create instances of this object
    @Inject
    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }
}

در قطعه کد بالا، به Dagger می‌گویید:

  1. نحوه ایجاد نمونه UserRepository با سازنده حاشیه‌نویسی‌شده @Inject.

  2. وابستگی‌های آن چیست: UserLocalDataSource و UserRemoteDataSource.

اکنون Dagger می‌داند چگونه نمونه‌ای از UserRepository ایجاد کند، اما نمی‌داند چگونه وابستگی‌های آن را ایجاد کند. اگر کلاس‌های دیگر را نیز حاشیه‌نویسی کنید، Dagger می‌داند چگونه آن‌ها را ایجاد کند:

کاتلین

// @Inject lets Dagger know how to create instances of these objects
class UserLocalDataSource @Inject constructor() { ... }
class UserRemoteDataSource @Inject constructor() { ... }

جاوا

public class UserLocalDataSource {
    @Inject
    public UserLocalDataSource() { }
}

public class UserRemoteDataSource {
    @Inject
    public UserRemoteDataSource() { }
}

عناصر Dagger

Dagger می‌تواند نموداری از وابستگی‌ها در پروژه‌تان ایجاد کند که می‌تواند از آن برای پیدا کردن جایی که باید آن وابستگی‌ها را درصورت نیاز دریافت کند استفاده کند. برای اینکه Dagger این کار را انجام دهد، باید یک میانای کاربری ایجاد کنید و آن را با @Component حاشیه‌نویسی کنید. Dagger محفظه‌ای ایجاد می‌کند که شما با تزریق وابستگی دستی ایجاد می‌کردید.

درون میانای @Component، می‌توانید توابعی را تعریف کنید که نمونه‌هایی از کلاس‌های موردنیازتان را برمی‌گردانند (یعنی UserRepository). @Component به Dagger می‌گوید که محفظه‌ای با همه وابستگی‌های لازم برای برآورده کردن انواع ارائه‌شده تولید کند. به این مؤلفه Dagger می‌گویند؛ این مؤلفه حاوی نموداری است که از اشیایی تشکیل شده است که Dagger می‌داند چگونه آن‌ها را ارائه دهد و وابستگی‌های مربوط به آن‌ها را می‌شناسد.

کاتلین

// @Component makes Dagger create a graph of dependencies
@Component
interface ApplicationGraph {
    // The return type  of functions inside the component interface is
    // what can be provided from the container
    fun repository(): UserRepository
}

جاوا

// @Component makes Dagger create a graph of dependencies
@Component
public interface ApplicationGraph {
    // The return type  of functions inside the component interface is
    // what can be consumed from the graph
    UserRepository userRepository();
}

وقتی پروژه را می‌سازید، Dagger پیاده‌سازی میانای ApplicationGraph را برای شما تولید می‌کند: DaggerApplicationGraph. Dagger با پردازشگر گزارمان خود، نمودار وابستگی‌ای ایجاد می‌کند که از روابط بین سه کلاس (UserRepository، UserLocalDatasource، و UserRemoteDataSource) با تنها یک نقطه ورودی تشکیل شده است: دریافت نمونه UserRepository. می‌توانید به‌صورت زیر از آن استفاده کنید:

کاتلین

// Create an instance of the application graph
val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()
// Grab an instance of UserRepository from the application graph
val userRepository: UserRepository = applicationGraph.repository()

جاوا

// Create an instance of the application graph
ApplicationGraph applicationGraph = DaggerApplicationGraph.create();

// Grab an instance of UserRepository from the application graph
UserRepository userRepository = applicationGraph.userRepository();

Dagger هربار که UserRepository درخواست می‌شود، نمونه جدیدی از آن ایجاد می‌کند.

کاتلین

val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()

val userRepository: UserRepository = applicationGraph.repository()
val userRepository2: UserRepository = applicationGraph.repository()

assert(userRepository != userRepository2)

جاوا

ApplicationGraph applicationGraph = DaggerApplicationGraph.create();

UserRepository userRepository = applicationGraph.userRepository();
UserRepository userRepository2 = applicationGraph.userRepository();

assert(userRepository != userRepository2)

گاهی اوقات، باید یک نمونه منحصربه‌فرد از یک وابستگی در یک ظرف داشته باشید. ممکن است به چند دلیل این را بخواهید:

  1. You want other types that have this type as a dependency to share the same instance, such as multiple ViewModel objects in the login flow using the same LoginUserData.

  2. ایجاد شیء هزینه زیادی دارد و نمی‌خواهید هربار که به‌عنوان وابستگی اعلام می‌شود، نمونه جدیدی ایجاد کنید (برای مثال، تجزیه‌کننده JSON).

در این مثال، ممکن است بخواهید یک نمونه منحصربه‌فرد از UserRepository در نمودار دردسترس باشد تا هربار که UserRepository را درخواست می‌کنید، همیشه همان نمونه را دریافت کنید. This is useful in your example because in a real-life application with a more complex application graph, you might have multiple ViewModel objects depending on UserRepository and you don't want to create new instances of UserLocalDataSource and UserRemoteDataSource every time UserRepository needs to be provided.

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

محدوده با Dagger

می‌توانید از حاشیه‌نویسی‌های دامنه برای محدود کردن طول عمر یک شیء به طول عمر مؤلفه آن استفاده کنید. این یعنی هر بار که لازم است آن نوع ارائه شود، از همان نمونه وابستگی استفاده می‌شود.

برای داشتن نمونه‌ای منحصربه‌فرد از UserRepository وقتی مخزن را در ApplicationGraph درخواست می‌کنید، از همان حاشیه‌نویسی دامنه برای میانای @Component و UserRepository استفاده کنید. می‌توانید از حاشیه‌نویسی @Singleton استفاده کنید که ازقبل با بسته javax.inject که Dagger استفاده می‌کند ارائه می‌شود:

کاتلین

// Scope annotations on a @Component interface informs Dagger that classes annotated
// with this annotation (i.e. @Singleton) are bound to the life of the graph and so
// the same instance of that type is provided every time the type is requested.
@Singleton
@Component
interface ApplicationGraph {
    fun repository(): UserRepository
}

// Scope this class to a component using @Singleton scope (i.e. ApplicationGraph)
@Singleton
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

جاوا

// Scope annotations on a @Component interface informs Dagger that classes annotated
// with this annotation (i.e. @Singleton) are scoped to the graph and the same
// instance of that type is provided every time the type is requested.
@Singleton
@Component
public interface ApplicationGraph {
    UserRepository userRepository();
}

// Scope this class to a component using @Singleton scope (i.e. ApplicationGraph)
@Singleton
public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    @Inject
    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }
}

یا می‌توانید حاشیه‌نویسی دامنه سفارشی ایجاد و استفاده کنید. می‌توانید حاشیه‌نویسی دامنه را به‌صورت زیر ایجاد کنید:

کاتلین

// Creates MyCustomScope
@Scope
@MustBeDocumented
@Retention(value = AnnotationRetention.RUNTIME)
annotation class MyCustomScope

جاوا

// Creates MyCustomScope
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomScope {}

سپس می‌توانید مثل قبل از آن استفاده کنید:

کاتلین

@MyCustomScope
@Component
interface ApplicationGraph {
    fun repository(): UserRepository
}

@MyCustomScope
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val service: UserService
) { ... }

جاوا

@MyCustomScope
@Component
public interface ApplicationGraph {
    UserRepository userRepository();
}

@MyCustomScope
public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    @Inject
    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }
}

در هر دو مورد، شیء با همان دامنه‌ای ارائه می‌شود که برای حاشیه‌نویسی واسط @Component استفاده شده است. بنابراین، هربار که applicationGraph.repository() را فراخوانی می‌کنید، همان نمونه UserRepository را دریافت می‌کنید.

کاتلین

val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()

val userRepository: UserRepository = applicationGraph.repository()
val userRepository2: UserRepository = applicationGraph.repository()

assert(userRepository == userRepository2)

جاوا

ApplicationGraph applicationGraph = DaggerApplicationGraph.create();

UserRepository userRepository = applicationGraph.userRepository();
UserRepository userRepository2 = applicationGraph.userRepository();

assert(userRepository == userRepository2)

نتیجه‌گیری

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

در صفحه بعدی، با نحوه افزودن Dagger به برنامه Android آشنا خواهید شد.