paint-brush
Dependency Injection Với Dagger 2: Nó Là Gì, Các Khái Niệm Chính Và Nhiều Hơn Nữatừ tác giả@dilip2882
412 lượt đọc
412 lượt đọc

Dependency Injection Với Dagger 2: Nó Là Gì, Các Khái Niệm Chính Và Nhiều Hơn Nữa

từ tác giả Dilip Patel21m2024/08/28
Read on Terminal Reader

dài quá đọc không nổi

Dependency Injection (DI) là một mẫu thiết kế được sử dụng để triển khai Inversion of Control (IoC). Đây là một kỹ thuật trong đó trách nhiệm tạo đối tượng được chuyển sang các phần khác của mã. Điều này thúc đẩy sự kết hợp lỏng lẻo, làm cho mã trở nên mô-đun hơn và dễ quản lý hơn.
featured image - Dependency Injection Với Dagger 2: Nó Là Gì, Các Khái Niệm Chính Và Nhiều Hơn Nữa
Dilip Patel HackerNoon profile picture
0-item


Giới thiệu về Dependency Injection

Dependency Injection (DI) là một mẫu thiết kế được sử dụng để triển khai Inversion of Control (IoC) trong đó quyền kiểm soát việc tạo và quản lý các phụ thuộc được chuyển từ ứng dụng sang một thực thể bên ngoài. Điều này giúp tạo ra mã có thể mô-đun hóa, có thể kiểm tra và bảo trì được nhiều hơn. Đây là một kỹ thuật trong đó trách nhiệm tạo đối tượng được chuyển sang các phần khác của mã. Điều này thúc đẩy sự kết hợp lỏng lẻo, làm cho mã có tính mô-đun hóa và dễ quản lý hơn.

Các lớp thường cần tham chiếu đến các lớp khác để hoạt động bình thường. Ví dụ, hãy xem xét một lớp Library yêu cầu một lớp Book . Các lớp cần thiết này được gọi là các phụ thuộc. Lớp Library phụ thuộc vào việc có một thể hiện của lớp Book để hoạt động.

hyperskill.org

Có ba cách chính để một lớp có thể lấy được các đối tượng cần thiết:

  1. Tự xây dựng : Lớp tạo và khởi tạo các phụ thuộc của riêng nó. Ví dụ, lớp Library sẽ tạo và khởi tạo phiên bản riêng của lớp Book .
  2. Truy xuất bên ngoài : Lớp truy xuất các phụ thuộc từ một nguồn bên ngoài. Một số API Android, chẳng hạn như Context getters và getSystemService() , hoạt động theo cách này.
  3. Dependency Injection : Dependency được cung cấp cho lớp, khi lớp được xây dựng hoặc thông qua các phương thức yêu cầu chúng. Ví dụ, hàm tạo Library sẽ nhận một thể hiện Book làm tham số.

Tùy chọn thứ ba là dependency injection! Với DI, bạn cung cấp các dependency của một lớp thay vì để chính thể hiện lớp đó lấy chúng.

Ví dụ không có Dependency Injection

Nếu không có DI, Library tạo ra sự phụ thuộc Book của riêng nó có thể trông như thế này:

 class Library { private Book book = new Book(); void open() { book.read(); } } public class Main { public static void main(String[] args) { Library library = new Library(); library.open(); } }

Đây không phải là ví dụ về DI vì lớp Library xây dựng Book của riêng nó. Điều này có thể gây ra vấn đề vì:

  • Liên kết chặt chẽ : LibraryBook được liên kết chặt chẽ. Một thể hiện của Library sử dụng một loại Book , khiến việc sử dụng các lớp con hoặc triển khai thay thế trở nên khó khăn.
  • Khó khăn khi kiểm thử : Sự phụ thuộc cứng vào Book khiến việc kiểm thử trở nên khó khăn hơn. Library sử dụng một phiên bản thực của Book , ngăn chặn việc sử dụng các bản sao thử nghiệm để sửa đổi Book cho các trường hợp thử nghiệm khác nhau.

Ví dụ với Dependency Injection

Với DI, thay vì mỗi phiên bản của Library xây dựng đối tượng Book riêng, nó sẽ nhận một đối tượng Book làm tham số trong hàm tạo của nó:

 class Library { private Book book; Library(Book book) { this.book = book; } void open() { book.read(); } } public class Main { public static void main(String[] args) { Book book = new Book(); Library library = new Library(book); library.open(); }

Hàm chính sử dụng Library . Vì Library phụ thuộc vào Book , ứng dụng tạo một thể hiện của Book và sau đó sử dụng nó để xây dựng một thể hiện của Library . Lợi ích của phương pháp dựa trên DI này là:

  • Khả năng tái sử dụng của Library : Bạn có thể truyền các triển khai khác nhau của Book vào Library . Ví dụ, bạn có thể định nghĩa một lớp con mới của Book có tên là EBook mà bạn muốn Library sử dụng. Với DI, bạn chỉ cần truyền một thể hiện của EBook vào Library và nó hoạt động mà không cần bất kỳ thay đổi nào nữa.
  • Kiểm tra Library dễ dàng: Bạn có thể truyền các bản sao thử nghiệm để kiểm tra các tình huống khác nhau.

Một ví dụ DI khác

Hãy xem xét một kịch bản trong đó lớp NotificationService dựa vào lớp Notification . Nếu không có DI, NotificationService sẽ trực tiếp tạo một thể hiện của Notification , khiến việc sử dụng các loại thông báo khác nhau hoặc kiểm tra dịch vụ bằng nhiều triển khai thông báo khác nhau trở nên khó khăn.

Để minh họa DI, chúng ta hãy tái cấu trúc ví dụ này:

 interface Notification { void send(); } class EmailNotification implements Notification { @Override public void send() { // Send email notification } } class SMSNotification implements Notification { @Override public void send() { // Send SMS notification } } class NotificationService { void sendNotification(Notification notification) { notification.send(); } }

Bây giờ, NotificationService phụ thuộc vào giao diện Notification chứ không phải một lớp cụ thể. Điều này cho phép các triển khai Notification khác nhau có thể được sử dụng thay thế cho nhau. Bạn có thể thiết lập triển khai bạn muốn sử dụng thông qua phương thức sendNotification :

 NotificationService service = new NotificationService(); service.sendNotification(new EmailNotification()); service.sendNotification(new SMSNotification());

Phương pháp tiêm phụ thuộc trong Android

Có ba loại DI chính:

  1. Phương thức (Giao diện) Tiêm : Các phụ thuộc được truyền qua các phương thức mà lớp có thể truy cập thông qua giao diện hoặc lớp khác. Ví dụ trước minh họa phương thức tiêm.
  2. Constructor Injection : Các phụ thuộc được truyền vào lớp thông qua hàm tạo của nó.
 class NotificationService { private final Notification notification; public NotificationService(Notification notification) { this.notification = notification; } public void sendNotification() { notification.send(); } } public class Main { public static void main(String[] args) { NotificationService service = new NotificationService(new EmailNotification()); service.sendNotification(); } }

3. Field Injection (hoặc Setter Injection) : Một số lớp khung Android, chẳng hạn như các hoạt động và các đoạn mã, được hệ thống khởi tạo, do đó không thể khởi tạo constructor injection. Với field injection, các dependency được khởi tạo sau khi lớp được tạo.

 class NotificationService { private Notification notification; public Notification getNotification() { return notification; } public void setNotification(Notification notification) { this.notification = notification; } public void sendNotification() { notification.send(); } } public class Main { public static void main(String[] args) { NotificationService service = new NotificationService(); service.setNotification(new EmailNotification()); service.sendNotification(); } }

4. Phương thức Injection : Các phụ thuộc được cung cấp thông qua các phương thức, thường sử dụng chú thích @Inject .

Ưu điểm của Dependency Injection

  • Các lớp trở nên có thể tái sử dụng nhiều hơn và ít phụ thuộc hơn vào các triển khai cụ thể. Điều này là do sự đảo ngược quyền kiểm soát, trong đó các lớp không còn quản lý các phụ thuộc của chúng nữa mà hoạt động với bất kỳ cấu hình nào được cung cấp.
  • Các phụ thuộc là một phần của bề mặt API và có thể được xác minh khi tạo đối tượng hoặc thời điểm biên dịch, giúp việc tái cấu trúc dễ dàng hơn.
  • Vì lớp không quản lý các phụ thuộc của nó nên các triển khai khác nhau có thể được truyền vào trong quá trình thử nghiệm để bao quát nhiều tình huống khác nhau.

Tự động tiêm phụ thuộc

Trong ví dụ trước, bạn đã tạo, cung cấp và quản lý thủ công các phụ thuộc của các lớp khác nhau mà không sử dụng thư viện. Cách tiếp cận này được gọi là tiêm phụ thuộc thủ công. Mặc dù nó hoạt động với các trường hợp đơn giản, nhưng nó trở nên cồng kềnh khi số lượng phụ thuộc và lớp tăng lên. Tiêm phụ thuộc thủ công có một số nhược điểm:

  • Boilerplate Code : Đối với các ứng dụng lớn, việc quản lý tất cả các phụ thuộc và kết nối chúng một cách chính xác có thể dẫn đến rất nhiều mã lặp lại. Trong kiến trúc nhiều lớp, việc tạo một đối tượng cho lớp trên cùng đòi hỏi phải cung cấp tất cả các phụ thuộc cho các lớp bên dưới nó. Ví dụ, để xây dựng một máy tính, bạn cần một CPU, một bo mạch chủ, RAM và các thành phần khác; và một CPU có thể cần bóng bán dẫn và tụ điện.
  • Quản lý phụ thuộc phức tạp : Khi bạn không thể xây dựng các phụ thuộc trước — chẳng hạn như với khởi tạo chậm hoặc phạm vi đối tượng cho các luồng cụ thể trong ứng dụng của bạn — bạn cần phải viết và duy trì một vùng chứa tùy chỉnh (hoặc biểu đồ phụ thuộc) để quản lý thời gian tồn tại của các phụ thuộc trong bộ nhớ.

Thư viện có thể tự động hóa quy trình này bằng cách tạo và cung cấp các phụ thuộc cho bạn. Các thư viện này được chia thành hai loại:

  1. Giải pháp dựa trên phản xạ : Kết nối các phụ thuộc khi chạy.
  2. Giải pháp tĩnh : Các giải pháp này tạo ra mã để kết nối các phụ thuộc tại thời điểm biên dịch.

Dagger là một thư viện dependency injection phổ biến cho Java, Kotlin và Android, được Google duy trì. Dagger đơn giản hóa DI trong ứng dụng của bạn bằng cách tạo và quản lý biểu đồ dependency cho bạn. Nó cung cấp các dependency hoàn toàn tĩnh, thời gian biên dịch, giải quyết nhiều vấn đề về phát triển và hiệu suất liên quan đến các giải pháp dựa trên phản chiếu như Guice.

Giải pháp dựa trên sự phản ánh

Các khung này kết nối các phụ thuộc khi chạy:

  1. Toothpick : Một khuôn khổ DI thời gian chạy sử dụng phản chiếu để kết nối các phụ thuộc. Nó được thiết kế để nhẹ và nhanh, phù hợp với các ứng dụng Android.

Giải pháp tĩnh

Các khung này tạo ra mã để kết nối các phụ thuộc tại thời điểm biên dịch:

  1. Hilt : Được xây dựng trên Dagger, Hilt cung cấp một cách chuẩn để kết hợp tiêm phụ thuộc Dagger vào ứng dụng Android. Nó đơn giản hóa việc thiết lập và sử dụng Dagger bằng cách cung cấp các thành phần và phạm vi được xác định trước .
  2. Koin : Một khuôn khổ DI nhẹ và đơn giản cho Kotlin. Koin sử dụng DSL để xác định các phụ thuộc và dễ thiết lập và sử dụng.
  3. Kodein : Một khuôn khổ DI dựa trên Kotlin dễ sử dụng và dễ hiểu. Nó cung cấp một API đơn giản và linh hoạt để quản lý các phụ thuộc.

Các giải pháp thay thế cho Dependency Injection

Một giải pháp thay thế cho dependency injection là mẫu service locator. Mẫu thiết kế này cũng giúp tách các lớp khỏi các dependency cụ thể của chúng. Bạn tạo một lớp được gọi là service locator để tạo và lưu trữ các dependency, cung cấp chúng theo yêu cầu.

 object ServiceLocator { fun getProcessor(): Processor = Processor() } class Computer { private val processor = ServiceLocator.getProcessor() fun start() { processor.run() } } fun main(args: Array<String>) { val computer = Computer() computer.start() }

Mẫu service locator khác với dependency injection ở cách dependency được sử dụng. Với mẫu service locator, các lớp yêu cầu các dependency mà chúng cần; với dependency injection, ứng dụng chủ động cung cấp các đối tượng cần thiết.

Dagger 2 là gì?

Dagger 2 là một DI framework phổ biến cho Android. Nó sử dụng thế hệ mã thời gian biên dịch và được biết đến với hiệu suất cao. Dagger 2 đơn giản hóa quá trình tiêm phụ thuộc bằng cách tạo mã cần thiết để xử lý các phụ thuộc, giảm mẫu và cải thiện hiệu quả.

Dagger 2 là một thư viện dựa trên chú thích để tiêm phụ thuộc trong Android. Sau đây là các chú thích chính và mục đích của chúng:

  • @Module : Được sử dụng để định nghĩa các lớp cung cấp các phụ thuộc. Ví dụ, một mô-đun có thể cung cấp ApiClient cho Retrofit.
  • @Provides : Chú thích các phương thức trong mô-đun để chỉ định cách tạo và trả về các phụ thuộc.
  • @Inject : Được sử dụng để yêu cầu các phụ thuộc. Có thể áp dụng cho các trường, hàm tạo và phương thức.
  • @Component : Một giao diện kết nối @Module@Inject . Giao diện này chứa tất cả các module và cung cấp trình xây dựng cho ứng dụng.
  • @Singleton : Đảm bảo một phiên bản duy nhất của sự phụ thuộc được tạo ra.
  • @Binds : Được sử dụng trong các lớp trừu tượng để cung cấp các phụ thuộc, tương tự như @Provides nhưng ngắn gọn hơn.

Linh kiện dao găm

Dagger có thể tạo biểu đồ phụ thuộc cho dự án của bạn, cho phép nó xác định nơi lấy các phụ thuộc khi cần. Để bật tính năng này, bạn cần tạo một giao diện và chú thích nó bằng @Component .

Trong giao diện @Component , bạn định nghĩa các phương thức trả về các thể hiện của các lớp bạn cần (ví dụ: BookRepository ). Chú thích @Component hướng dẫn Dagger tạo một vùng chứa với tất cả các phụ thuộc cần thiết để đáp ứng các kiểu mà nó hiển thị. Vùng chứa này được gọi là thành phần Dagger và nó chứa một biểu đồ các đối tượng mà Dagger biết cách cung cấp cùng với các phụ thuộc của chúng.


Ví dụ

Hãy cùng xem xét một ví dụ liên quan đến LibraryRepository :

  1. Chú thích cho hàm tạo : Thêm chú thích @Inject vào hàm tạo LibraryRepository để Dagger biết cách tạo một thể hiện của LibraryRepository .
 public class LibraryRepository { private final LocalLibraryDataSource localDataSource; private final RemoteLibraryDataSource remoteDataSource; @Inject public LibraryRepository(LocalLibraryDataSource localDataSource, RemoteLibraryDataSource remoteDataSource) { this.localDataSource = localDataSource; this.remoteDataSource = remoteDataSource; } }

2. Chú thích các phụ thuộc : Tương tự như vậy, hãy chú thích các hàm tạo của các phụ thuộc ( LocalLibraryDataSourceRemoteLibraryDataSource ) để Dagger biết cách tạo chúng.

 public class LocalLibraryDataSource { @Inject public LocalLibraryDataSource() { // Initialization code } } public class RemoteLibraryDataSource { private final LibraryService libraryService; @Inject public RemoteLibraryDataSource(LibraryService libraryService) { this.libraryService = libraryService; } }

3. Xác định Thành phần : Tạo một giao diện được chú thích bằng @Component để xác định biểu đồ phụ thuộc.

 @Component public interface ApplicationComponent { LibraryRepository getLibraryRepository(); }

Khi bạn xây dựng dự án, Dagger sẽ tạo ra một triển khai giao diện ApplicationComponent cho bạn, thường được đặt tên là DaggerApplicationComponent .

Cách sử dụng

Bây giờ bạn có thể sử dụng thành phần được tạo để lấy các phiên bản của lớp với các phụ thuộc được tự động đưa vào:

 public class MainApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.create(); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } }

Trong hoạt động hoặc đoạn mã của bạn, bạn có thể truy xuất phiên bản LibraryRepository :

 public class MainActivity extends AppCompatActivity { @Inject LibraryRepository libraryRepository; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((MainApplication) getApplication()).getApplicationComponent().inject(this); // Use the injected libraryRepository } }

Các khái niệm chính trong Dagger 2

1. Các mô-đun
∘ Các khái niệm chính của Mô-đun
∘ Bao gồm các mô-đun trong các thành phần
2. Phạm vi
3. Thành phần
4. Phụ thuộc thành phần
5. Liên kết thời gian chạy

1. Các mô-đun

Module trong Dagger 2 là các lớp được chú thích bằng @Module cung cấp các phụ thuộc cho các thành phần. Chúng chứa các phương thức được chú thích bằng @Provides hoặc @Binds để chỉ định cách tạo và cung cấp các phụ thuộc. Module rất cần thiết để tổ chức và quản lý việc tạo các đối tượng mà ứng dụng của bạn cần.

Các khái niệm chính của mô-đun

  1. @Module Annotation: Chú thích này được sử dụng để định nghĩa một lớp là một mô-đun Dagger. Một lớp mô-đun chứa các phương thức cung cấp các phụ thuộc.
  2. @Provides Annotation: Chú thích này được sử dụng trên các phương thức trong một mô-đun để chỉ ra rằng phương thức cung cấp một sự phụ thuộc nhất định. Các phương thức này chịu trách nhiệm tạo và trả về các thể hiện của các sự phụ thuộc.
  3. Chú thích @Binds: Chú thích này được sử dụng trong các lớp trừu tượng để liên kết một triển khai với một giao diện. Nó ngắn gọn hơn @Provides và được sử dụng khi mô-đun là một lớp trừu tượng.

Ví dụ về một mô-đun

 @Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } @Provides @Singleton OkHttpClient provideOkHttpClient() { return new OkHttpClient.Builder() .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) .build(); } }

Trong ví dụ này, NetworkModule là một lớp được chú thích bằng @Module . Nó chứa hai phương thức được chú thích bằng @Provides để tạo và trả về các thể hiện của RetrofitOkHttpClient .

Sử dụng @Binds

Khi bạn có một giao diện và triển khai của nó, bạn có thể sử dụng @Binds để liên kết triển khai với giao diện. Điều này ngắn gọn hơn so với sử dụng @Provides .

 public interface ApiService { void fetchData(); } public class ApiServiceImpl implements ApiService { @Override public void fetchData() { // Implementation } } @Module public abstract class ApiModule { @Binds abstract ApiService bindApiService(ApiServiceImpl apiServiceImpl); }

Trong ví dụ này, ApiModule là một lớp trừu tượng được chú thích bằng @Module . Phương thức bindApiService được chú thích bằng @Binds để liên kết ApiServiceImpl với ApiService .

Các mô-đun có thể được sắp xếp dựa trên chức năng mà chúng cung cấp. Ví dụ, bạn có thể có các mô-đun riêng cho hoạt động mạng, hoạt động cơ sở dữ liệu và các phụ thuộc liên quan đến UI.

Ví dụ:

  • NetworkModule : Cung cấp các phụ thuộc liên quan đến mạng như RetrofitOkHttpClient .
  • DatabaseModule : Cung cấp các phụ thuộc liên quan đến cơ sở dữ liệu như RoomDatabase .
  • UIModule : Cung cấp các phụ thuộc liên quan đến UI như ViewModelPresenter .

Bao gồm các mô-đun trong các thành phần

Các mô-đun được bao gồm trong các thành phần để cung cấp các phụ thuộc cho các lớp cần chúng. Sau đây là cách bạn có thể thiết lập:

ApplicationComponent.java:

 @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }

Trong ví dụ này, ApplicationComponent bao gồm NetworkModuleDatabaseModule để cung cấp các phụ thuộc cho ứng dụng.

2. Phạm vi

Phạm vi trong Dagger 2 là các chú thích xác định vòng đời của các phụ thuộc. Chúng đảm bảo rằng một phiên bản duy nhất của một phụ thuộc được tạo và chia sẻ trong một phạm vi được chỉ định. Điều này giúp quản lý bộ nhớ hiệu quả và đảm bảo rằng các phụ thuộc được sử dụng lại khi thích hợp.

  • Phạm vi Singleton : Đảm bảo duy nhất một trường hợp phụ thuộc trong suốt vòng đời của ứng dụng.
  • Phạm vi hoạt động : Đảm bảo một trường hợp duy nhất của sự phụ thuộc trong vòng đời của một hoạt động.
  • Phạm vi phân đoạn : Đảm bảo một phiên bản duy nhất của sự phụ thuộc trong vòng đời của một phân đoạn.

1. Phạm vi Singleton

Định nghĩa : Phạm vi @Singleton đảm bảo rằng một phiên bản duy nhất của sự phụ thuộc được tạo và chia sẻ trong toàn bộ vòng đời của ứng dụng.

Phạm vi này thường được sử dụng cho các phụ thuộc cần được chia sẻ trên toàn bộ ứng dụng, chẳng hạn như máy khách mạng, phiên bản cơ sở dữ liệu hoặc tùy chọn chia sẻ.

Ví dụ:

 @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }

Trong ví dụ này, chú thích @Singleton đảm bảo rằng các phiên bản RetrofitDatabase do NetworkModuleDatabaseModule cung cấp là các phiên bản đơn lẻ và được chia sẻ trên toàn bộ ứng dụng.

2. Phạm vi hoạt động

Định nghĩa : @ActivityScope (phạm vi tùy chỉnh) đảm bảo rằng một phiên bản duy nhất của sự phụ thuộc được tạo và chia sẻ trong vòng đời của một hoạt động.

Phạm vi này hữu ích cho các phụ thuộc cụ thể đối với một hoạt động và cần được tạo lại mỗi khi hoạt động được tạo lại, chẳng hạn như trình bày hoặc mô hình xem.

Ví dụ :

 @Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope { } @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); }

Trong ví dụ này, chú thích @ActivityScope đảm bảo rằng các phụ thuộc do ActivityModule cung cấp sẽ nằm trong phạm vi vòng đời của hoạt động.

3. Phạm vi phân đoạn

Định nghĩa : @FragmentScope (một phạm vi tùy chỉnh khác) đảm bảo rằng một phiên bản duy nhất của sự phụ thuộc được tạo và chia sẻ trong vòng đời của một đoạn mã.

Trường hợp sử dụng: Phạm vi này hữu ích cho các phụ thuộc cụ thể đối với một phân đoạn và cần được tạo lại mỗi khi phân đoạn được tạo lại, chẳng hạn như trình bày cụ thể cho phân đoạn hoặc mô hình xem.

Ví dụ :

 @Scope @Retention(RetentionPolicy.RUNTIME) public @interface FragmentScope { } @FragmentScope @Component(dependencies = ActivityComponent.class, modules = FragmentModule.class) public interface FragmentComponent { void inject(MyFragment myFragment); }

Trong ví dụ này, chú thích @FragmentScope đảm bảo rằng các phụ thuộc do FragmentModule cung cấp sẽ nằm trong phạm vi vòng đời của đoạn mã.

3. Thành phần

Sự phụ thuộc thành phần cho phép một thành phần phụ thuộc vào thành phần khác, cho phép tái sử dụng các phụ thuộc. Có hai loại phụ thuộc thành phần chính:

  • Thành phần ứng dụng : Cung cấp các phụ thuộc cần thiết trong toàn bộ ứng dụng.
  • Thành phần hoạt động : Cung cấp các phụ thuộc cần thiết trong một hoạt động cụ thể.

1. Thành phần ứng dụng

Định nghĩa : Thành phần ứng dụng cung cấp các phụ thuộc cần thiết trong toàn bộ ứng dụng. Nó thường được giới hạn trong @Singleton để đảm bảo rằng các phụ thuộc được chia sẻ trên toàn bộ ứng dụng.

Thành phần này được sử dụng cho các phụ thuộc cần có sẵn trên toàn cầu, chẳng hạn như máy khách mạng, phiên bản cơ sở dữ liệu hoặc tùy chọn chia sẻ.

Ví dụ :

 @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }

Trong ví dụ này, ApplicationComponent chịu trách nhiệm cung cấp các phiên bản RetrofitDatabase được chia sẻ trên toàn bộ ứng dụng.

2. Thành phần hoạt động

Định nghĩa : Thành phần Hoạt động cung cấp các phụ thuộc cần thiết trong một hoạt động cụ thể. Nó thường được định phạm vi với phạm vi tùy chỉnh, chẳng hạn như @ActivityScope , để đảm bảo rằng các phụ thuộc được tạo lại mỗi khi hoạt động được tạo lại.

Thành phần này được sử dụng cho các phụ thuộc cụ thể cho một hoạt động, chẳng hạn như người trình bày hoặc mô hình xem.

Ví dụ :

 @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); }

Trong ví dụ này, ActivityComponent phụ thuộc vào ApplicationComponent và cung cấp các phụ thuộc cụ thể cho MainActivity .

4. Phụ thuộc thành phần

Sự phụ thuộc thành phần cho phép một thành phần phụ thuộc vào thành phần khác, cho phép tái sử dụng các phụ thuộc. Có hai loại phụ thuộc thành phần chính:

  • Thành phần phụ : Thành phần phụ là thành phần con của thành phần khác và có thể truy cập vào các phần phụ thuộc của thành phần cha.
  • Thuộc tính phụ thuộc : Thuộc tính này cho phép một thành phần phụ thuộc vào thành phần khác mà không cần phải là thành phần phụ.

1. Các thành phần phụ:

Một thành phần con là thành phần con của một thành phần khác và có thể truy cập vào các phụ thuộc của thành phần cha. Các thành phần con được định nghĩa trong thành phần cha và có thể kế thừa phạm vi của thành phần cha.

Ví dụ :

 @ActivityScope @Subcomponent(modules = ActivityModule.class) public interface ActivitySubcomponent { void inject(MainActivity mainActivity); }

Trong ví dụ này, ActivitySubcomponent là một thành phần con của thành phần cha và có thể truy cập vào các phần phụ thuộc của nó.

2. Thuộc tính phụ thuộc

Điều này cho phép một thành phần phụ thuộc vào một thành phần khác mà không phải là thành phần phụ. Thành phần phụ thuộc có thể truy cập vào các phụ thuộc do thành phần cha cung cấp.

Ví dụ :

 @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); }

Trong ví dụ này, ActivityComponent phụ thuộc vào ApplicationComponent và có thể truy cập vào các phần phụ thuộc của nó.

5. Liên kết thời gian chạy

Liên kết thời gian chạy trong Dagger 2 đề cập đến việc cung cấp các phụ thuộc được tạo và quản lý tại thời gian chạy, dựa trên bối cảnh cần đến chúng.

  • Bối cảnh ứng dụng : Được sử dụng cho các phụ thuộc cần tồn tại lâu dài như ứng dụng.
  • Bối cảnh hoạt động : Được sử dụng cho các phụ thuộc cần tồn tại lâu dài như một hoạt động.

1. Bối cảnh ứng dụng

Định nghĩa : Ngữ cảnh ứng dụng là ngữ cảnh gắn liền với vòng đời của toàn bộ ứng dụng. Ngữ cảnh này được sử dụng cho các phụ thuộc cần tồn tại lâu dài như chính ứng dụng.

Các phụ thuộc được chia sẻ trên toàn bộ ứng dụng và không cần phải tạo lại cho từng hoạt động hoặc đoạn. Ví dụ bao gồm máy khách mạng, phiên bản cơ sở dữ liệu và tùy chọn chia sẻ.

Ví dụ :

 @Module public class AppModule { private final Application application; public AppModule(Application application) { this.application = application; } @Provides @Singleton Application provideApplication() { return application; } @Provides @Singleton Context provideApplicationContext() { return application.getApplicationContext(); } }

Trong ví dụ này, AppModule cung cấp ngữ cảnh ứng dụng dưới dạng phụ thuộc singleton. Phương thức provideApplicationContext đảm bảo rằng ngữ cảnh được cung cấp được gắn với vòng đời của ứng dụng.

2. Bối cảnh hoạt động

Định nghĩa : Ngữ cảnh hoạt động là ngữ cảnh gắn liền với vòng đời của một hoạt động cụ thể. Ngữ cảnh này được sử dụng cho các phụ thuộc cần tồn tại lâu dài như chính hoạt động đó.

Các phụ thuộc cụ thể cho một hoạt động và cần được tạo lại mỗi lần hoạt động được tạo lại. Ví dụ bao gồm mô hình xem, trình bày và các phụ thuộc liên quan đến UI.

Ví dụ :

 @Module public class ActivityModule { private final Activity activity; public ActivityModule(Activity activity) { this.activity = activity; } @Provides @ActivityScope Activity provideActivity() { return activity; } @Provides @ActivityScope Context provideActivityContext() { return activity; } }

Trong ví dụ này, ActivityModule cung cấp ngữ cảnh hoạt động dưới dạng phụ thuộc có phạm vi. Phương thức provideActivityContext đảm bảo rằng ngữ cảnh được cung cấp được gắn với vòng đời của hoạt động.

Sử dụng Runtime Bindings trong Components

Để sử dụng các ràng buộc thời gian chạy này, bạn cần đưa các mô-đun tương ứng vào các thành phần của mình:

Thành phần ứng dụng :

 @Singleton @Component(modules = {AppModule.class, NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); Context getApplicationContext(); }

Thành phần hoạt động :

 @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); Context getActivityContext(); }

Tiêm ngữ cảnh

Sau khi thiết lập các thành phần và mô-đun, bạn có thể đưa ngữ cảnh vào các lớp khi cần.

Ví dụ trong một Hoạt động :

 public class MainActivity extends AppCompatActivity { @Inject Context activityContext; @Inject Context applicationContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ApplicationComponent appComponent = ((MyApplication) getApplication()).getApplicationComponent(); ActivityComponent activityComponent = DaggerActivityComponent.builder() .applicationComponent(appComponent) .activityModule(new ActivityModule(this)) .build(); activityComponent.inject(this); // Use the injected contexts Log.d("MainActivity", "Activity Context: " + activityContext); Log.d("MainActivity", "Application Context: " + applicationContext); } }

Trong ví dụ này, MainActivity nhận được cả ngữ cảnh hoạt động và ngữ cảnh ứng dụng thông qua dependency injection. Điều này cho phép hoạt động sử dụng ngữ cảnh phù hợp dựa trên nhu cầu cụ thể của các phụ thuộc.

Ví dụ: Sử dụng Dagger 2 trong ứng dụng Android

Thiết lập Dagger 2

Để sử dụng Dagger 2 trong dự án của bạn, bạn cần thêm các phụ thuộc sau vào tệp build.gradle :

 dependencies { implementation 'com.google.dagger:dagger:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x' }

Thay thế 2.x bằng phiên bản mới nhất của Dagger 2.

Bước 1: Xác định một Mô-đun

Tạo một mô-đun để cung cấp các phụ thuộc. Ví dụ: một NetworkModule để cung cấp một thể hiện Retrofit :

 @Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } }

Bước 2: Xác định một thành phần

Tạo một thành phần để kết nối mô-đun và các lớp cần sự phụ thuộc:

 @Singleton @Component(modules = {NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }

Bước 3: Tiêm các phụ thuộc

Sử dụng thành phần để đưa các phụ thuộc vào các lớp của bạn. Ví dụ, trong lớp Application của bạn:

 public class MyApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.builder() .networkModule(new NetworkModule()) .build(); applicationComponent.inject(this); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } }

Bước 4: Sử dụng các phụ thuộc được tiêm

Bây giờ bạn có thể sử dụng các dependency được inject trong các lớp của mình. Ví dụ, trong một Activity :

 public class MainActivity extends AppCompatActivity { @Inject Retrofit retrofit; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((MyApplication) getApplication()).getApplicationComponent().inject(this); // Use the injected Retrofit instance // ... } }

Phần kết luận

Chúng ta hãy tóm tắt chủ đề này:

  • Mục đích chính của DI là nới lỏng sự liên kết, giúp quản lý các mối phụ thuộc dễ dàng hơn.
  • Bằng cách sử dụng DI, bạn có thể tăng tính linh hoạt của mã và đơn giản hóa quy trình thử nghiệm.
  • DI là một chủ đề phức tạp với nhiều cách triển khai khác nhau tùy theo từng kịch bản.
  • DI ở các ngôn ngữ khác nhau có những đặc điểm riêng có thể ảnh hưởng đến cách bạn làm việc với nó.
  • Dagger 2 tự động hóa quá trình tạo và cung cấp các phụ thuộc, giảm mã chuẩn và cải thiện khả năng bảo trì.
  • Dagger 2 cung cấp tính năng an toàn khi biên dịch, đảm bảo rằng mọi phụ thuộc đều được đáp ứng trước khi ứng dụng chạy.
  • Bằng cách tạo mã tại thời điểm biên dịch, Dagger 2 tránh được chi phí hiệu suất liên quan đến các giải pháp dựa trên phản xạ.