Dependency Injection? Hiểu về Dependency Injection

2116

Những dự án với độ phức tạp cao ngoài việc thiết kế tính năng cho ứng dụng, tổ chức code luôn luôn là vấn đề được đặt lên hàng đầu. Tổ chức tốt giúp lập trình viên dễ dàng bảo trì, cũng như mở rộng code về sau.

Để có thể tiết kiệm chi phí và thời gian cho công đoạn này nhưng vẫn đem lại hiệu quả cao, việc nắm vững về các design pattern sẽ giúp ích rất nhiều. Dependency Injection là một dạng design pattern được thiết kế với mục đích ngăn chặn sự phụ thuộc giữa các class.

Pattern này được dùng trong cả C#, Java, Php (có thư viện auryn), Javascript (Cho Nodejs có 2 DI là Di-Ninjaknifecycle) và các ngôn ngữ khác nên các bạn cứ áp dụng thoải mái.

Dependency Injection là gì?

Dependency injection (DI) là một kỹ thuật lập trình giúp tách một class độc lập với các biến phụ thuộc. Với lập trình hướng đối tượng, chúng ta hầu như luôn phải làm việc với rất nhiều class trong một chương trình. Các class được liên kết với nhau theo một mối quan hệ nào đó. Dependency là một loại quan hệ giữa 2 class mà trong đó một class hoạt động độc lập và class còn lại phụ thuộc bởi class kia.

Dependency Injection

Có 3 loại dependency injection phổ biến

Dependency Injection (DI) hướng đến một mục đích duy nhất là tạo ra ứng dụng ít kết dính (loosely coupling), dễ mở rộng (flexibility) cũng như giúp lập trình viên tập trung chủ yếu vào công việc product flow.

  • Constructor Injection: các biến phụ thuộc được cung cấp thông qua một hàm tạo lớp.
  • Setter Injection: Các dependency sẽ được truyền vào 1 class thông qua các hàm Setter.
  • Interface Injection: Class cần inject sẽ implement 1 interface. Interface này chứa 1 hàm tên Inject. Container sẽ injection dependency vào 1 class thông qua việc gọi hàm Inject của interface đó.

Note: Tham khảo thêm ý kiến từ anh Hoàng Toidicodedao.com

Hiện nay, các lập trình viên hay lẫn lộn giữa các khái niệm Dependency Inversion, Inversion of Control (IoC), Dependency Injection (DI). Ba khái niệm này tương tự nhau nhưng không hoàn toàn giống nhau.

Sự khác biệt giữa 3 khái niệm trên:

  • Dependency Inversion: Đây là một nguyên lý để thiết kế và viết code.
  • Inversion of Control: Đây là một design pattern được tạo ra để code có thể tuân thủ nguyên lý Dependency Inversion. Có nhiều cách hiện thực pattern này: ServiceLocator, Event, Delegate, … Dependency Injection là một trong các cách đó.
  • Dependency Injection: Đây là một cách để hiện thực Inversion of Control Pattern (Có thể coi nó là một design pattern riêng cũng được). Các module phụ thuộc (dependency) sẽ được inject vào module cấp cao.

Cách hiện thực dependency injection (DI)

Dependency Injection có thể được hiện thực dựa trên các quy tắc sau:

  • Các class sẽ không phụ thuộc trực tiếp lẫn nhau mà thay vào đó chúng sẽ liên kết với nhau thông qua một Interface hoặc base class (đối với một số ngôn ngữ không hỗ trợ Interface)
  • Việc khởi tạo các class sẽ do các Interface quản lí thay vì class phụ thuộc nó

Ví dụ DI:

Chúng ta có một lớp car chứa các đối tượng khác nhau như Wheels, Battery, v.v.

Ở đây, class Car có trách nhiệm tạo ra tất cả các đối tượng phụ thuộc (dependency objects). Bây giờ, điều gì sẽ xảy ra nếu chúng ta quyết định bỏ MRFWheels trong tương lai và muốn sử dụng Casumina Wheels?

Lúc này chúng ta phải tạo lại đối tượng car mới với phụ thuộc mới (new dependecy) Casumina. Rồi sau nữa bạn lại muốn độ bánh xe lên, hay thay bánh khác thì sao??? Mỗi lần vậy thêm một loạt code và khi đó chưa chắc chúng đã chạy được chưa kể là cực kỳ khó đọc.

Dependency Injection là một dạng design pattern được thiết kế nhằm ngăn chặn sự phụ thuộc nêu trên, điều này giúp giảm chi phí trong việc sửa đổi và nâng cấp hệ thống. Nhờ vậy khi bạn thực thiện thay đổi một class A thì những class chứa biến kiểu class A cũng không cần phải thay đổi theo.

Nếu ta sử dụng DI cho ví dụ trên, có thể thay đổi Wheels ngay tại runtime. Nó làm cho class Car của chúng ta độc lập với việc tạo các đối tượng của Wheels, Battery, v.v.

DI containers

Ngày nay có nhiều DI containers được sử dụng. Phổ biến trong Java là Spring, hay Unity cũng thế … Có thể hiểu một cách đơn giản DI containers là một phần quan trọng của một framework. Nó như một thùng để chứa các component dependency của ứng dụng một project. Một minh họa khá đơn giản DI containers trong Spring framework.

Trong đó POJOs là những class (object) java thuần. Configuration Metadata là các file config ta sẽ khai báo các component dependency nào sẽ tạo instance và “lưu” vào container khi ứng dụng start.

Một số lợi ích và hạn chế từ việc sử dụng DI

  1. Dễ test và viết Unit Test: Dễ hiểu là khi ta có thể Inject các dependency vào trong một class thì ta cũng dễ dàng “tiêm” các mock object vào class (được test) đó.
  2. Dễ dàng thấy quan hệ giữa các object: Dependency Injection sẽ inject các object phụ thuộc vào các interface thành phần của object bị phụ thuộc nên ta dễ dàng thấy được các dependency của một object.
  3. Dễ dàng hơn trong việc mở rộng các ứng dụng
  4. Giảm sự kết dính giữa các thành phần, tránh ảnh hưởng quá nhiều khi có thay đổi nào đó

Một số hạn chế của DI

  1. Kỹ thuật này tương đối phức tạp để học hỏi và sử dụng, và nếu lạm dụng DI có thể dẫn đến các vấn đề về quản lý và một số vấn đề khác.
  2. Đôi khi sẽ khó debug. Vì sửa dụng các interface nên có thể gặp khó khăn khi ta debug source code vì không biết implement nào thực sự được truyền vào.
  3. Vì Dependency Injection ẩn các dependency nên một số lỗi chỉ xảy ra khi chạy chương trình thay vì có thể phát hiện khi biên dịch chương trình.
  4. Khó khăn lớn nhất là khi người mới vào làm bằng DI sẽ không hiểu rõ ràng tư tưởng, khiến quá trình làm DI vẫn bị nhập nhằng và các injector bị ràng buộc mà không thoát hẳn ra theo tư tưởng của DI.
  Java và những điều thú vị có thể bạn chưa biết
  Làm thế nào tạo instance của một class mà không gọi từ khóa new?

Tài liệu tham khảo

SHARE