DEPENDENCY INJECTION VÀ INVERSION OF CONTROL
Trong quá trình học, hầu như chúng ta(sinh viên IT) đều được học một số khái niệm OOP cơ bản như: Abstraction (Tính trừu tượng) Encapsulation (Tính bao đóng) Inheritance (Tính kế thừa) Polymophirsm (Tính đa hình) Có 4 tính chất cơ bản trong OOP như trên mà có lẽ tất cả chúng ta đều đã dược ...
Trong quá trình học, hầu như chúng ta(sinh viên IT) đều được học một số khái niệm OOP cơ bản như:
- Abstraction (Tính trừu tượng)
- Encapsulation (Tính bao đóng)
- Inheritance (Tính kế thừa)
- Polymophirsm (Tính đa hình) Có 4 tính chất cơ bản trong OOP như trên mà có lẽ tất cả chúng ta đều đã dược dạy qua, các tính chất khá là rõ ràng và hầu như các buổi phỏng vấn của sinh viên mới ra trường đều có những câu hỏi liên quan đến khái niệm này.Trong quá trình học và làm việc để tiến thêm 1 bước trên con đường thành senior thì việc lắm vững những nguyên lý thiết kế trong OOP là tất yếu. SOLID, đây là những nguyên lý được rút ra từ hàng ngàn dự án thành công và thất bại. Một project áp dụng những nguyên lý này sẽ có code dễ đọc, dễ test, rõ ràng hơn. Và việc quan trọng nhất là việc maintainace code sẽ dễ hơn rất nhiều, nó là tập hợp 5 nguyên tắc sau đây: 1.Single responsibility principle 2.Open/closed principle 3.Liskov substitution principle 4.Interface segregation principle 5.Dependency inversion principle Nguyên lý cuối cùng trong SOLID chính là Dependency Inversion,và Dependency Injection chỉ là 1 trong những pattern để hiện thực Dependency Inversion.
- Dependency Inversion: Đây là một nguyên lý để thiết kế và viết code đó là:
1. Các module cấp cao không nên phụ thuộc vào các modules cấp thấp. Cả 2 nên phụ thuộc vào abstraction. 2. Interface (abstraction) không nên phụ thuộc vào chi tiết, mà ngược lại. ( Các class giao tiếp với nhau thông qua interface, không phải thông qua implementation.)
- 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.
- Khi nói tới DI, tức là nói tới Depedency Injection. Hiện nay, một số DI container như Unity, StructureMap v…v, hỗ trợ chúng ta trong việc cài đặt và áp dụng Dependency Injection vào code (Sẽ nói ở bài sau), tuy nhiên vẫn có thể gọi chúng là IoC Container, ý nghĩa tương tự nhau.
- Có thể hiểu Dependency Injection một cách đơn giản như sau:
- Các module không giao tiếp trực tiếp với nhau, mà thông qua interface. Module cấp thấp sẽ implement interface, module cấp cao sẽ gọi module cấp thấp. Ví dụ: Để giao tiếp với database, ta có interface IDatabase, các module cấp thấp là XMLDatabase, SQLDatabase. Module cấp cao là CustomerBusiness sẽ sử dụng interface IDatabase.
- Việc khởi tạo các module cấp thấp sẽ do DI Container thực hiện. Ví dụ: Trong module CustomerBusiness, ta sẽ không khởi tạo IDatabase db = new XMLDatabase(), việc này sẽ do DI Container thực hiện. Module CustomerBusiness sẽ không biết gì về module XMLDatabase hay SQLDatabase.
- Việc Module nào gắn với interface nào sẽ được config trong code hoặc trong file XML. DI được dùng để làm giảm sự phụ thuộc giữa các module, dễ dàng hơn trong việc thay đổi module, bảo trì code và testing.
Có 3 dạng Dependency Injection:
- Constructor Injection: Các dependency sẽ được container 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.
- 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 đó. Đây là cách rườm rà và ít được sử dụng nhất.
- Example:
public class ConnectionUtil implements Connection{ private SqlServerConnection connect; public ConnectionUtil() { this.connect = new sqlServerConnection(); } public void connect() { connect.open(); } }
Chúng ta có thể dễ dàng thấy được Class ConnectionUtil này có nhiệm vụ là kết nối đến hệ quản trị cơ sở dữ liệu là MS SQL Server, trong class ConnectionUtil có một thuộc tính là connect với kiểu sqlServerConnection và được khởi tạo trong constructor của class này. Tuy nhiên, điều này sẽ khiến cho ConnectionUtil bị liên kết chặt chẽ với sqlServerConnection. Ở đây, sqlServerConnection chính là một Dependency (phụ thuộc) của ConnectionUtil, hay nói cách khác là ConnectionUtil bị phụ thuộc vào sqlServerConnection.Nếu chỉ connect đến MS SQL SERVER thì xong, done! Nhưng chuyện gì xảy ra nếu như chúng ta muốn kết nối đến MYSQL SERVER, POSTGRESQL, hoặc ORACLE... Như vậy, class ConnectionUtil sẽ phải khởi tạo các đối tượng có kiểu mysqlConnection, oracleConnection,... trong constructor. Điều này sẽ khiến cho class ConnectionUtil phình to ra, code trở nên phức tạp hơn rất nhiều. Hơn nữa, chúng ta có thể thấy new sqlServerConnection() là một implementation của sqlServerConnection. Khi chúng ta muốn thay đổi implementation của sqlServerConnection, Lúc này chúng ta lại bắt buộc phải động đến ConnectionUtil, hoặc là phải duplicate ra một implementation mới.
- Với Spring Dependency Injection ta có thể giải quyết điều này trong vòng 1 nốt nhạc:
public class ConnectionUtil implements Connection { private SqlConnection connect; public ConnectionUtil(SqlConnection connect) { this.connect = connect; } public void connect() { connect.open(); } }
Chúng ta có thể thấy,ConnectionUtil không hề khởi tạo một đối tượng nhiệm vụ cụ thể nào trong constructor của mình. Thay vào đó, class này truyền connect như là một tham số vào constructor tại thời điểm khởi tạo. Đây là một kiểu của Dependency Injection được biết với tên constructor injection. Trường dữ liệu connect của connect có kiểu là SqlConnection, một interface mà tất cả các class khác sẽ implement. Do đó, ConnectionUtil có thể connect đến bất kì hệ quản trị cơ sở dữ liệu nào từ ms sql server, oracle, mysql,... miễn là connection đó có implement. Điểm mấu chốt ở đây là ConnectionUtil không liên kết với bất kỳ một implementation cụ thể nào của connect. Người ta gọi đó là liên kết lỏng, đây cũng là lợi ích chính của Dependency Injection. Nếu một đối tượng chỉ biết về các dependency (phụ thuộc) của nó thông qua interface của các dependency này (chứ không phải implementation của chúng), khi đó các dependency có thể dễ dàng thay đổi implementation của mình mà không làm ảnh hưởng đến đối tượng ban đầu.
Trong bài này mình đã giới thiệu về DEPENDENCY INJECTION VÀ INVERSION OF CONTROL cũng như cách thức sử dụng nó trong Spring. Đây là một khái niệm cơ bản nhưng vô cùng quan trọng nếu muốn tìm hiểu hoặc làm việc sâu hơn với Spring. Thank you!