Trang chủ Program Design Pattern – Dependency Injection (P1)

Design Pattern – Dependency Injection (P1)

bởi Tran Tung

Trong lập trình chúng ta thường xuyên gặp các class có sự liên kết với nhau, các liên kết này được gọi là Dependency, dẫn đến việc khi thay đổi 1 class thì các class phụ thuộc vào nó sẽ bị ảnh hưởng và điều này vi phạm nguyên lý Dependency Inversion  chính là chữ D trong SOLID.
Để giải quyết vấn đề này thì chúng ta phải tìm cách ngăn chặn việc phụ thuộc lẫn nhau hoặc cắt giảm tối đa sự phụ thuộc. Và Dependency Injection đã được sinh ra để giải quyết vấn đề này, chúng ta cùng đi vào tìm hiểu DI và các khái niệm liên quan đến nó.

Dependency Inversion Principle (DIP), Inversion of Control (IoC), Dependency Injection (DI) là gì

Có thể mọi người đã từng nghe nói đến Dependency Inversion Principle, Inversion of Control, Dependency Injection và có thể bị nhầm lẫn giữa các khái niệm trên do ba khái niệm này khá tương tự nhau.
Chúng ta cùng xem lại nguyên lý Dependency Inversion trong SOLID, nội dung của nó là:

  • Các module cấp cao không nên phụ thuộc vào các module cấp thấp, cả hai nên phụ thuộc vào abstraction.
  • Abstractions không nên phụ thuộc vào chi tiết mà ngược lại, chi tiết nên phụ thuộc vào Abstractions. Các class giao tiếp với nhau qua interface, không phải qua implementation.
    Còn Inversion of Control: là một design partten được tạo ra để code có thể tuân thủ nguyên lý Dependency Inversion. Có nhiều các để thực hiện partten này như Service Locator, Event, Delegate …và Dependency Injection là một trong những cách đó.

Đơn giản chúng ta chỉ cần nhớ:“DI is about wiring, IoC is about direction, and DIP is about shape.”

Tiếp theo chúng ta đi vào chi tiết Dependency Injection.Dependency Injection (DI) là một design partten giúp loại bỏ sự phụ thuộc giữa các module, làm cho việc thay đổi, bảo trì, test dễ dàng hơn.

Nhiệm vụ của DI:

  • Tạo các đối tượng
  • Quản lý sự phụ thuộc giữa các đối tượng
  • Cung cấp các service được yêu cầu bổi đối tượng

Nguyên tắc hoạt động của DI:

  • 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 thông qua interface.
  • Việc khởi tạo các module cấp thấp sẽ do DI Container/ IoC Container thực hiện.
  • Việc Module nào gắn với interface nào sẽ được config trong file properties, trong file XML hoặc thông qua Annotation.
Cách cài đặt:
Các thành phần chính:
  • Client : là một class cần sử dụng Service.
  • Service : là một class/ interface cung cấp service/ dependency cho Client.
  • ServiceImpl: cài đặt các phương thực cụ thể của Service.
  • Injector: là một lớp chịu trách nhiệm khởi tạo các service và inject các thể hiện này cho Client.
Các 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.
  • Fields/ properties: Các dependency sẽ được truyền vào 1 class một cách trực tiếp vào các field.
  • 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à cũng ít được sử dụng.
  • Service Locator: nó hoạt động như một mapper, cho phép thay đổi code tại thời điểm run-time mà không cần biên dịch lại ứng dụng hoặc phải khởi động lại.
Ví dụ sử dụng Dependency Injection

Ví dụ chúng ta làm tính năng đăng nhập cho game

1
2
3
4
5
6
7
8
9
10
11
public class FacebookService
{
    public FacebookService()
    {
    }
 
    public void DoLogin(string userId)
    {
        Console.WriteLine("user " + userId + " do login FB");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class LoginManager
{
    private FacebookService _facebookService;
 
    public LoginManager()
    {
        _facebookService = new FacebookService();
    }
 
    public void Login(string userId)
    {
        _facebookService.DoLogin(userId);
    }
}

Như bên trên chỉ có 2 class rất đơn giản, tuy nhiên có 1 số hạn chế sau:

  • Mỗi khi class FacebookService có sự thay đổi thì rất có thể class LoginManager cũng thay đổi theo
  • Không muốn login bằng FB nữa, đổi phương thức login bằng Google chẳng hạn

Ta sẽ thử áp dụng theo đúng phần phần cài đặt của DI như bên trên để giải quyết

Tạo interface cho service

1
2
3
4
public interface ILoginService
{
    void DoLogin(string userId);
}

Tạo các implement của interface

1
2
3
4
5
6
7
8
9
10
11
public class FacebookService : ILoginService
 {
     public FacebookService()
     {
     }
  
     public void DoLogin(string userId)
     {
         Console.WriteLine("user " + userId + " do login FB");
     }
 }
1
2
3
4
5
6
7
8
9
10
11
public class GoogleService : ILoginService
{
    public GoogleService()
    {
    }
 
    public void DoLogin(string userId)
    {
        Console.WriteLine("user " + userId + " do login GG");
    }
}

Tạo client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class LoginManager
{
    private ILoginService _loginService;
 
    public LoginManager(ILoginService loginService)
    {
        _loginService = loginService;
    }
 
    public void Login(string userId)
    {
        _loginService.DoLogin(userId);
    }
}

Injector: nơi tạo service và inject vào client

1
2
3
4
5
6
7
8
9
10
public class MainProgram
{
    private LoginManager _loginManager;
 
    public MainProgram()
    {
        _loginManager = new LoginManager(new FacebookService());
        _loginManager.Login("userId_123");
    }
}

Chúng ta có thể thấy thay vì tạo 1 instance cụ thể của class FacebookService trong LoginManager chúng ta sẽ dùng 1 interface, và service này được truyền từ bên ngoài vào LoginManager. Lúc này không còn sự phụ thuộc giữa LoginManager và phần service Login, chúng ta có thể thay đổi, thêm phương thức login một cách dễ dàng mà không cần động vào phần code đã có. Ví dụ bên trên chính là Constructor Injection, một dạng của DI.

Tuy nhiên, hạn chế của ví dụ bên trên là chúng ta phải khởi tạo 1 instace cụ thể của service ở nhiều nơi nếu có nhiều chỗ gọi đến nó, để giải quyết vấn đề này chúng ta có thể áp dụng Inversion of Control Container (IoC container), ServiceLocator partten

Ưu điểm của Dependency Injection
  • Reduced dependencies: giảm sự kết dính giữa các module.
  • Reusable: code dễ bảo trì, dễ tái sử dụng, thay thế module. Giảm boiler-plate code do việc tạo các biến phụ thuộc đã được injector thực hiện.
  • Readable: dễ dàng thấy quan hệ giữa các module vì các dependecy đều được inject vào constructor.
  • Testable: rất dễ test và viết Unit Test.
và khuyết điểm là:
  • Khái niệm DI khá khó hiểu đối với người mới tìm hiểu.
  • Sử dụng interface nên đôi khi sẽ khó debug, do không biết chính xác module nào được gọi.
  • Có thể gặp lỗi ở run-time thay vì compile-time.
  • Các object được khởi tạo toàn bộ ngay từ đầu, có thể làm giảm performance.

http://tutorials.jenkov.com/dependency-injection/index.html

https://martinfowler.com/articles/dipInTheWild.html

 

Hướng dẫn Java Design Pattern – Dependency Injection

 

Dependency Injection và Inversion of Control – Phần 1: Định nghĩa

Nhấn để đánh giá bài viết!
[Số đánh giá: 1 Trung bình: 5]

Có thể bạn quan tâm

Để lại bình luận