Tại sao dùng dependency injection

Ý tưởng chính của Dependency Injection đó là bạn không phụ thuộc vào ai cả và người khác cũng không phụ thuộc vào bạn. Khi cần mình sẽ gọi bạn và bạn cũng vậy.

Tại sao dùng dependency injection
Tại sao dùng dependency injection

Để mình nói cụ thể hơn nhé!

  SQL Injection là gì? Cách giảm thiểu và phòng ngừa SQL Injection

  Codepen là gì ? Hướng dẫn sử dụng Codepen cơ bản

Ngày xưa mình thường viết code như thế này:

Giả sử bạn có một đối tượng là Circle, đối tượng này có một phương thức là draw() như sau:

public class Circle { public void draw() { System.out.println("Drawing circle ..."); } }

Giờ mình muốn sử dụng đối tượng này để vẽ hình tròn, mình sẽ khởi tạo đối tượng Circle ngay trong constructor và mình sẽ code như sau:

public class Drawing { public Circle circle; public Drawing() { circle = new Cirle(); } public void preparing() { System.out.println("Preparing ..."); } public void draw() { circle.draw(); } public static void main(String[] args) { Drawing drawing = new Drawing(); drawing.draw(); } }

Rõ ràng bạn thấy đối tượng Drawing của mình đang phụ thuộc vào đối tượng Circle của bạn (nghĩa là mình đang phụ thuộc vào bạn đấy), bởi vì mỗi khi chạy ứng dụng, đối tượng Drawing lại phải giữ luôn thông tin của đối tượng Circle. Đây là nhược điểm thứ nhất của cách code này.

Nhược điểm thứ hai đó là nếu sau này mình muốn vẽ một tam giác, mình lại phải đi khai báo lại đối tượng khác để đáp ứng nhu cầu của mình. Đối tượng Drawing của mình vì thế cứ phải thay đổi liên tục theo nhu cầu.

Vậy giờ làm sao để giải quyết hai nhược điểm này?

Nhược điểm thứ hai chúng ta có thể giải quyết dễ dàng bằng cách sử dụng interface. Đối tượng Circle của bạn giờ sẽ hiện thực một interface tên là Shape, cụ thể như sau:

public interface Shape { public void draw(); }

public class Circle implement Shape { public void draw() { System.out.println("Drawing circle ..."); } }

Sau này mình muốn vẽ tam giác thì chỉ cần viết thêm một lớp mới và hiện thực interface Shape là được.

Đối với nhược điểm thứ nhất, Dependency Injection sinh ra để giải quyết nó.

Hãy xem Dependency Injection giải quyết như thế nào nhé, mình xin sửa lại đối tượng Drawing như sau:

public class Drawing { private Shape shape; public Drawing(Shape shape) { this.shape = shape; } public void setShape(Shape shape) { this.shape = shape; } public void preparing() { System.out.println("Preparing ..."); } public void draw() { shape.draw(); } }

Các bạn thấy đó, nếu bây giờ mình chỉ muốn đối tượng Drawing của mình chỉ chuẩn bị các dụng cụ để vẽ mà thôi, mình sẽ không cần gọi đến đối tượng Circle của bạn, code sẽ như sau:

Dependency Inject là 1 kỹ thuật, 1 design pattern cho phép xóa bỏ sự phụ thuộc hard-code và làm cho ứng dụng của bạn dễ mở rộng và maintain hơn.

Để hiểu định nghĩa trên, mình có ví dụ sau:

Mình có 1 ứng dụng gọi tới object của class MySQLDAO(class MySQLDAO chuyên thực hiện truy vấn với cơ sở dữ liệu MySQL của ứng dụng)

Bây giờ bạn muốn truy vấn tới cơ sở dữ liệu postgre. Bạn phải xóa khai báo MySQLDAO trong ứng dụng và thay bằng PostgreDAO, sau đó muốn dùng lại MySQLDAO bạn lại làm ngược lại… rõ ràng code sẽ phải sửa lại và test nhiều lần.

Giải pháp dùng if-else kiểm tra điều kiện sẽ dùng đối tượng DAO nào… nhưng sau đấy có thêm một DAO khác ví dụ như MSSQLDAO chẳng hạn… phức tạp hơn nhiều phải không.

(Thường thì ít khi 1 ứng dụng dùng nhiều loại cơ sở dữ liệu khác nhau nhưng sẽ có trường hợp sử dụng nhiều database, mình để thành nhiều loại cơ sở dữ liệu khác nhau cho dễ hình dung)

Dependency Injection (DI) là gì? Code ví dụ bằng Java

Dependency Inject chính là để giải quyết cho trường hợp như thế này.

Trong ví dụ trên ta tạo 1 interface AbstractDAO và cho các class DAO kia thừa kế AbstractDAO. Bây giờ trong các class sử dụng DAO ta khai báo AbstractDAO, tùy theo điều kiện tương ứng AbstractDAO có thể là MySQLDAO hoặc PostgreDAO.

Việc thay thế AbstractDAO bằng MySQLDAO/PostgreDAO được gọi là injection.

Tại sao dùng dependency injection

Dependency Injection (DI) là gì? Code ví dụ bằng Java

2. Ví dụ.

Mình sẽ code demo ví dụ trên:

Tạo một interface để khai báo các method giao tiếp với database:

public interface AbstractDAO { void insert(); void delete(); void update(); }

Tạo các class DAO tương ứng với từng loại database và implements các method của AbstractDAO

public class MySQLDAO implements AbstractDAO { @Override public void insert() { System.out.println("MySQL insert"); } @Override public void delete() { System.out.println("MySQL delete"); } @Override public void update() { System.out.println("MySQL update"); } }

public class PostgreDAO implements AbstractDAO { @Override public void insert() { System.out.println("Postgre insert"); } @Override public void delete() { System.out.println("Postgre delete"); } @Override public void update() { System.out.println("Postgre update"); } }

public class MSSQLDAO implements AbstractDAO { @Override public void insert() { System.out.println("MSSQL insert"); } @Override public void delete() { System.out.println("MSSQL delete"); } @Override public void update() { System.out.println("MSSQL update"); } }

file config.properites lưu thông tin config quyết định sẽ kết nối tới database nào.

## 1: MySQL | 2: Postgre | 3: MSSQL database=2

Bây giờ ở class cần dùng đến dao ta sẽ khai báo AbstractDAO, tùy theo tham số trong file config mà ta khởi tạo đối tượng AbstractDAO là MySQLDAO, PostgreDAO hay MSSQLDAO.

public class Client { AbstractDAO dao; public Client() { dao = FactoryDAO.getDAO(); } public AbstractDAO getDao() { return dao; } public void setDao(AbstractDAO dao) { this.dao = dao; } public void execute() { dao.insert(); dao.update(); dao.delete(); } }

Như các bạn thấy ở đây mình dùng Factory Pattern để quyết định đối tượng được tạo ra. Class FactoryDAO (Factory class) sẽ đọc file và quyết định đối tượng nào được tạo ra.

public class FactoryDAO { public static AbstractDAO getDAO() { Properties prop = new Properties(); InputStream input = null; try { input = new FileInputStream("source/config.properties"); // load a properties file prop.load(input); // get the database value String database = prop.getProperty("database"); if (database.equals("1")) { return new MySQLDAO(); } if (database.equals("2")) { return new PostgreDAO(); } if (database.equals("3")) { return new MSSQLDAO(); } } catch (IOException ex) { ex.printStackTrace(); return null; } return null; } }

Demo ví dụ trên:

public class MainApp { public static void main(String[] args) { Client client = new Client(); client.execute(); } }

Kết quả:

Postgre insert Postgre update Postgre delete

Sửa giá trị database trong file config.properties bằng 1 và chạy lại:

MySQL insert MySQL update MySQL delete

Bây giờ nếu bạn có thêm 1 loại database khác cần sử dụng thì chỉ cần tạo DAO cho nó, implements AbstractDAO và sửa lại FactoryDAO là được.

Ví dụ mình có thêm database DB2.

mình sẽ tạo class DB2DAO.java implements AbstractDAO.

trong method getDAO() của FactoryDAO thêm điều kiện nếu database = 4 thì sẽ trả về DB2DAO.

rõ ràng cách làm này giúp ta sẽ mở rộng ứng dụng hơn rất nhiều và mỗi lần thay đổi đối tượng dao ta không cần phải khởi động/deploy lại ứng dụng mà chỉ cần thay đổi thông tin trong file config.properties.

Việc lấy thông tin từ file config.properites rồi quyết định tạo đối tượng trong class Client.java chính là tiêm sự phụ thuộc (Dependency Injection – DI)

Download code ví dụ trên tại đây

3. Các phương pháp thực hiện Dependency Injection.

Các phương pháp cơ bản để Dependency Injection.

  • Constructor Injection: Các dependency sẽ được truyền vào (inject vào) 1 class thông qua constructor của class đó. Đây là cách thông dụng nhất. (ví dụ trên mình dùng theo cách này)
  • Setter Injection: Các dependency sẽ được truyền vào 1 class thông qua các hàm Setter/Getter

Ví dụ ở trên mình sẽ sửa method getDao ở class Client.java thành

public class MySQLDAO implements AbstractDAO { @Override public void insert() { System.out.println("MySQL insert"); } @Override public void delete() { System.out.println("MySQL delete"); } @Override public void update() { System.out.println("MySQL update"); } } 0

  • Public fields: Các dependency sẽ được truyền vào 1 class một cách trực tiếp vào các public field. Cách này ít được sử dụng nhất. Ví dụ ở trên khi khai báo AbstractDAO ở class Client.java thành

public class MySQLDAO implements AbstractDAO { @Override public void insert() { System.out.println("MySQL insert"); } @Override public void delete() { System.out.println("MySQL delete"); } @Override public void update() { System.out.println("MySQL update"); } } 1Dependency Injection (DI) là gì? Code ví dụ bằng Java

4. Ưu nhược điểm của Dependency Injection

Ưu điểm
  • Giảm sự kết dính giữa các module
  • Code dễ bảo trì, dễ thay thế module
  • Rất dễ test và viết Unit Test
  • Dễ dàng thấy quan hệ giữa các module (Vì các dependecy đều được inject vào constructor)
Nhược điểm
  • Khái niệm DI hơi khó hiểu với người mới
  • Khó debug vì không biết implements nào của interface được gọi đến
  • Các object được khởi tạo từ đầu làm giảm performance
  • Làm tăng độ phức tạp của code

Do đó với những ứng dụng nhỏ gọn, làm ăn luôn thì ko nên áp dụng DI, còn những ứng dụng cần sự linh hoạt, mở rộng, maintain thì sử dụng DI.

Dependency injection là gì Viblo?

Dependency Injection - Đúng như tên gọi khi google dich: Tiêm phụ thuộc =)) một kĩ thuật xử lý truyền tham số vào khi xử lí tịa thời điểm runtime thay vì truyền vào tại thời điểm compile time, nó giúp class đó không cần phải nhớ những lớp nào mà nó phụ thuộc, khi nào cần thì nó mới gọi vào.

Đâu là mô tả rõ nhất về cơ chế dependency injection?

Bạn có thể hiểu như sau: Dependency Injection chính người trung gian có trách nhiệm tạo ra những loại wheel khác nhau rồi cung cấp chúng cho class car. Việc này sẽ làm cho class Car không cần phải quá phụ thuộc vào bất kỳ một wheels hoặc Battery cụ thể nào nữa.

Dependency injection là gì Tedu?

Vậy depedency injection là gì? Inversion of Control chỉ một khái niệm cho một nhóm các nguyên tắc thiết kế hướng tới việc loại bỏ sự phụ thuộc trong code. Nó làm việc bằng cách tự động tạo các instance của các thành phần phụ thuộc ở các module khác và đặt vào một nơi gọi container.

Dependency injection trong java là gì?

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, để khiến cho code dễ hiểu hơn, trực quan hơn, nhằm phục vụ cho mục đích bảo trì và nâng cấp code.