Dependency injection với Dagger 2 - Giới thiệu về DI
Bài đăng này đã không được cập nhật trong 3 năm
Thời gian trước, tại Google I/O Extended ở Tech Space đã giới thiệu về dependency injection với Dagger 2.
Dependency injection
Dependency injection là tất cả việc tạo các đối tượng (object) và truyền chúng cho nơi cần sử dụng. Quan sát ví dụ: class UserManager
với 2 phụ thuộc là UserStore
và ApiService
. Không thực hiện Dependency injection sẽ như sau:
Cả hai đối tượng UserStore
và ApiService
được khởi tạo bên trong class UserManager
:
class UserManager {
private ApiService apiService;
private UserStore userStore;
//No-args constructor. Dependencies are created inside.
public UserManager() {
this.apiService = new ApiSerivce();
this.userStore = new UserStore();
}
void registerUser() {/* */}
}
class RegisterActivity extends Activity {
private UserManager userManager;
@Override
protected void onCreate(Bundle b) {
super.onCreate(b);
this.userManager = new UserManager();
}
public void onRegisterClick(View v) {
userManager.registerUser();
}
}
Đoạn code trên có vấn đề gì? Hãy tưởng tượng rằng, bạn muốn thay đổi cài đặt của UserStore
bằng cơ chế lưu trữ của SharedPreferences
. Nó sẽ cần đối tượng Context
để tạo một thể hiện vì vậy hàm khởi tạo của UserStore
sẽ cần truyền thêm tham số. Có nghĩa là bên trong class UserManager
sẽ phải cập nhật lại lời gọi tạo đối tượng UserStore
. Khi đó, nếu có hàng chục class mà sử dụng UserStore
sẽ đều phải cập nhật lại.
Bây giờ, quan sát class UserManager
có thực hiện Dependency injection:
Các phụ thuộc được tạo và cung cấp bên ngoài class:
class UserManager {
private ApiService apiService;
private UserStore userStore;
//Dependencies are passed as arguments
public UserManager(ApiService apiService, UserStore userStore) {
this.apiService = apiService;
this.userStore = userStore;
}
void registerUser() {/* */}
}
class RegisterActivity extends Activity {
private UserManager userManager;
@Override
protected void onCreate(Bundle b) {
super.onCreate(b);
ApiService api = ApiService.getInstance();
UserStore store = UserStore.getInstance();
this.userManager = new UserManager(api, store);
}
public void onRegisterClick(View v) {
userManager.registerUser();
}
}
Khi đó, trong vấn đề tương tự, chúng ta thay đổi cài đặt của một phụ thuộc sẽ không cần cập nhật lại code của class UserManager
. Tất cả các phụ thuộc của nó được truyền vào từ bên ngoài thông qua tham số (paramater), vì vậy chỉ có phần code khởi tạo đối tượng UserStore
là phải được cập nhật lại.
Vậy, những lợi thế của việc sử dụng dependency injection là gì?
Tách biệt khởi tạo/sử dụng
Chúng ta khởi tạo các thể nghiệm của class chỉ một lần - thường là ở nơi khác với nơi sử dụng đối tượng. Nhờ cách tiếp cận này mà code của chúng ta được module hoá hơn, tất cả các phụ thuộc có thể được thay thế một cách đơn giản mà không có tác động lên logic của ứng dụng. Chẳng hạn, bạn muốn thay đổi DatabaseUserStore
để sử dụng SharedPrefsUserStore
, bạn chỉ cần quan tâm đến giao tiếp API (giống như DatabaseUserStore
) hoặc cài đặt cùng một interface.
Sử dụng cho Unit test
Các unit test giả định rằng các class được test trong sự cô lập hoàn toàn mà không hề biết về các sự phụ thuộc của nó. Chẳng hạn, sau đây là một unit test cho class UserManager
:
public class UserManagerTests {
UserManager userManager;
@Mock
ApiService apiServiceMock;
@Mock
UserStore userStoreMock;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
userManager = new UserManager(apiServiceMock, userStoreMock);
}
@After
public void tearDown() {
}
@Test
public void testSomething() {
//Test our userManager here - all its dependencies are satisfied
}
}
Phát triển độc lập/ đồng thời
Nhờ có việc module hoá code (UserStore
có thể được cài đặt độc lập với UserManager
) mà việc chia code để các lập trình viên cùng phát triển là dễ dàng. Mọi người chỉ phải quan tâm đến interface của UserStore
(đặc biệt là các phương thức public của UserStore
sử dụng trong UserManager
).
Dependency injection frameworks
Bên cạnh những thuận lợi thì mẫu dependency injection cũng có một số nhược điểm. Một trong số đó là lượng code boilerplate lớn hơn. Hãy tưởng tượng đơn giản, class LoginActivity
được cài đặt với mô hình MVP. Class này có thể trông như sau:
Phần code chỉ chịu trách nhiệm khởi tạo LoginActivityPresenter
như sau:
public class LoginActivity extends AppCompatActivity {
LoginActivityPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
OkHttpClient okHttpClient = new OkHttpClient();
RestAdapter.Builder builder = new RestAdapter.Builder();
builder.setClient(new OkClient(okHttpClient));
RestAdapter restAdapter = builder.build();
ApiService apiService = restAdapter.create(ApiService.class);
UserManager userManager = UserManager.getInstance(apiService);
UserDataStore userDataStore = UserDataStore.getInstance(
getSharedPreferences("prefs", MODE_PRIVATE)
);
//Presenter is initialized here
presenter = new LoginActivityPresenter(this, userManager, userDataStore);
}
}
Trông không được thân thiện. Và đây là vấn đề mà DI sẽ giải quyết. Phần code có sử dụng DI như sau:
public class LoginActivity extends AppCompatActivity {
@Inject
LoginActivityPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Satisfy all dependencies requested by @Inject annotation
getDependenciesGraph().inject(this);
}
}
Và như vậy, code sẽ đơn giản hơn rất nhiều.
Kết luận
Như vậy, chúng ta đã tìm hiểu thế nào là dependency, thế nào là dependency injection. Việc sử dụng DI với mô hình MVP làm cho code được module hoá nhiều hơn, tính tách biệt được nâng cao và dễ dàng trong việc maintain và mở rộng phần mềm.
Tham khảo
https://medium.com/azimolabs/dagger-2-on-production-reducing-methods-count-5a13ff671e30
All rights reserved