Giới Thiệu Về Dependency Injection (DI): Định Nghĩa, Lợi Ích và Cách Sử Dụng

Dependency Injection (DI) là một khái niệm quan trọng trong phát triển phần mềm hiện đại. Bài viết này sẽ cung cấp một cái nhìn tổng quan về DI, giải thích nó là gì, tại sao nó hữu ích và cách bạn có thể áp dụng nó trong dự án của mình.

Theo Wikipedia:

Dependency injection là một kỹ thuật trong đó một đối tượng (hoặc một phương thức tĩnh) cung cấp các dependency của một đối tượng khác. Một dependency là một đối tượng mà có thể được sử dụng (một service).

Định nghĩa này có vẻ hơi khó hiểu, vậy nên chúng ta hãy cùng nhau khám phá sâu hơn về DI.

“Dependency” có nghĩa là sự phụ thuộc vào một cái gì đó để hoạt động. Ví dụ, nếu bạn phụ thuộc nhiều vào điện thoại thông minh, bạn có thể nói rằng bạn “dependent” vào nó.

Trước khi đi sâu vào Dependency Injection, hãy tìm hiểu “dependency” có nghĩa là gì trong lập trình. Khi một class A sử dụng một số chức năng của class B, chúng ta có thể nói rằng class A có một quan hệ phụ thuộc (dependency) với class B.

Mô tả quan hệ phụ thuộc giữa Class A và Class BMô tả quan hệ phụ thuộc giữa Class A và Class B

Trong Java, trước khi bạn có thể sử dụng một phương thức của một class khác, bạn phải khởi tạo một đối tượng của class đó. Nói cách khác, class A cần tạo một thực thể của class B.

Vậy, Dependency Injection là việc chuyển giao nhiệm vụ khởi tạo đối tượng (dependency) cho một thành phần khác, và đối tượng (class A) chỉ việc sử dụng các dependency đó.

Mô tả Dependency Injection: chuyển giao khởi tạo objectMô tả Dependency Injection: chuyển giao khởi tạo object

Tại Sao Nên Sử Dụng Dependency Injection?

Hãy xem xét một ví dụ: giả sử bạn có một class Car, trong đó chứa các đối tượng khác như Wheel, Battery,…

 class Car{
    private Wheels wheel = new MRFWheels();
    private Battery battery = new ExcideBattery();
    ...
    ...
 }

Trong ví dụ này, class Car chịu trách nhiệm khởi tạo tất cả các đối tượng dependency. Nhưng điều gì sẽ xảy ra nếu bạn muốn thay thế MRFWheel bằng YokohamaWheel? Bạn sẽ cần tạo một class Car mới với YokohamaWheel.

Tuy nhiên, với Dependency Injection, bạn có thể thay đổi Wheel ở runtime, bởi vì dependency có thể được “inject” vào ở runtime thay vì compile time.

Hãy nghĩ về Dependency Injection như một người trung gian chịu trách nhiệm tạo ra các loại Wheel khác nhau và cung cấp chúng cho class Car. Việc này giúp class Car không còn phụ thuộc vào một loại Wheel hay Battery cụ thể nào nữa.

Các Loại Dependency Injection Phổ Biến

Về cơ bản, có ba loại Dependency Injection:

  1. Constructor Injection: Các dependency được cung cấp thông qua constructor của class.
  2. Setter Injection: Client tạo ra một setter method để các class khác có thể sử dụng để cung cấp dependency.
  3. Interface Injection: Dependency cung cấp một hàm injector để inject nó vào bất kỳ client nào được truyền vào. Các client phải implement một interface mà có một setter method để nhận dependency.
 class Car{
    private Wheels wheel;
    private Battery battery;

    /*
     Ở đâu đó trong project, ta khởi tạo những objects mà đc yêu cầu bởi class này
     Có 2 cách để implement dependency injection
     1. Dựa vào constructor
     2. Dựa vào Setter method
     */

    // Dựa vào constructor
    Car(Wheel wh, Battery bt) {
        this.wheel = wh;
        this.battery = bt;
    }

    // Dựa vào Setter method
    void setWheel(Battery bt){
        this.battery = bt;
    }
    ...
    ...
 }

Vậy trách nhiệm của Dependency Injection là:

  1. Tạo ra các object (dependency).
  2. Xác định class nào cần những object đó.
  3. Cung cấp cho các class đó những object mà chúng cần.

Bằng cách này, nếu một object có sự thay đổi trong tương lai, Dependency Injection sẽ có nhiệm vụ cung cấp lại những object cần thiết cho class.

Lợi Ích Của Dependency Injection

  • Dễ dàng viết Unit Test: DI giúp bạn dễ dàng thay thế các dependency thực bằng các mock object trong quá trình kiểm thử, từ đó cô lập và kiểm tra từng thành phần một cách độc lập.
  • Giảm thiểu Boilerplate Code: Việc khởi tạo dependency được thực hiện bởi một component khác, giúp code của bạn gọn gàng và dễ đọc hơn.
  • Mở Rộng Dự Án Dễ Dàng Hơn: Với DI, bạn có thể dễ dàng thêm hoặc thay đổi các dependency mà không cần sửa đổi code của các class phụ thuộc.
  • Liên Kết Lỏng (Loose Coupling): DI giúp giảm sự phụ thuộc lẫn nhau giữa các thành phần trong dự án, làm cho hệ thống linh hoạt và dễ bảo trì hơn.

Nhược Điểm Của Dependency Injection

  • Phức Tạp Để Học: DI có thể là một khái niệm khó nắm bắt ban đầu, đặc biệt đối với những người mới bắt đầu lập trình.
  • Chuyển Lỗi Compile-Time Sang Runtime: Một số lỗi có thể không được phát hiện cho đến khi runtime, vì việc liên kết các dependency xảy ra tại thời điểm đó.
  • Ảnh Hưởng Đến IDE: DI có thể làm ảnh hưởng đến chức năng auto-complete hoặc “Find references” của một số IDE.

Kết Luận

Dependency Injection là một kỹ thuật mạnh mẽ có thể giúp bạn xây dựng các ứng dụng linh hoạt, dễ bảo trì và dễ kiểm thử. Mặc dù có một số nhược điểm, lợi ích mà DI mang lại thường vượt trội hơn. Hy vọng bài viết này đã giúp bạn hiểu rõ hơn về Dependency Injection và cách áp dụng nó trong dự án của mình.