Dependency Injection là gì?


Article Featured Image

Dependency Injection là gì?

Trước khi bắt đầu với Dependency Injection, các bạn hãy đọc lại bài viết về SOLID principles, những nguyên lý thiết kế và viết code. Nguyên lý cuối cùng trong SOLID chính là Dependency Inversion:

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)

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.

Khi nói tới DI, tức là nói tới Depedency Injection. 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 thông qua interface.
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ẽ chỉ 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.

Ví dụ minh họa
Tham khảo đoạn code thông thường sau

Cách làm này có gì sai không? Có vẻ là không, viết code cũng nhanh nữa. Nhưng cách viết này “có thể” sẽ dẫn tới một số vấn đề trong tương lai:

  • Rất khó test hàm Checkout này, vì nó dính dáng tới cả hai module Database và EmailSender.
  • Trong trường hợp ta muốn thay đổi module Database, EmailSender,… ta phải sửa toàn bộ các chỗ khởi tạo và gọi các module này. Việc làm này rất mất thời gian, dễ gây lỗi.
  • Về lâu dài, code sẽ trở nên “kết dính”, các module có tính kết dính cao, một module thay đổi sẽ kéo theo hàng loạt thay đổi. Đây là nỗi ác mộng khi phải maintain code.
Inversion of Control và Dependency Injection đã ra đời để giải quyết những vấn đề này.
Đầu tiên ta lần lượt tạo các interface IDatabase, IEmailSender, ILogger, các class kia ban đầu sẽ lần lượt kế thừa những interface này. Để dễ hiểu, giờ mình sẽ tạm gọi  IDatabase, IEmailSender, ILogger là Interface, các class như Database, EmailSender, Logger là Module

Sau đó các Module cấp thấp sẽ được inject (truyền vào) vào Module cấp cao thông qua Constructor hoặc thông qua Properties. Ta không gọi toán tử new để khởi tạo instance, mà instance đó sẽ được truyền từ ngoài vào (Truyền manual, hoặc nhờ DI Container).
  // Interface
  public interface IDatabase
  {
  void Save(int orderId);
  }
  public interface ILogger
  {
  void LogInfo(string info);
  }
  public interface IEmailSender
  {
  void SendEmail(int userId);
  }
   
  // Các Module implement các Interface
  public class Logger : ILogger
  {
  public void LogInfo(string info) {}
  }
  public class Database : IDatabase
  {
  public void Save(int orderId) {}
  }
  public class EmailSender : IEmailSender
  {
  public void SendEmail(int userId) {}
  }
   
  public class Cart
  {
  private readonly IDatabase _db;
  private readonly ILogger _log;
  private readonly IEmailSender _es;
   
  public Cart(IDatabase db, ILogger log, IEmailSender es)
  {
  _db = db;
  _log = log;
  _es = es;
  }
   
  public void Checkout(int orderId, int userId)
  {
  _db.Save(orderId);
  _log.LogInfo("Order has been checkout");
  _es.SendEmail(userId);
  }
  }
   
  // Khi sử dụng không dùng DI container
  //Dependency Injection một cách đơn giản nhất
  Cart myCart = new Cart(new Database(),new Logger(), new EmailSender());
  //Khi cần thay đổi database, logger
  myCart = new Cart(new XMLDatabase(),new FakeLogger(), new FakeEmailSender());
   
  // Khi sử dụng dùng DI container (Thực tế ta dùng cách này)
   
  //Với mỗi Interface, ta define một Module tương ứng
  DIContainer.SetModule<IDatabase, Database>();
  DIContainer.SetModule<ILogger, Logger>();
  DIContainer.SetModule<IEmailSender, EmailSender>();
   
  DIContainer.SetModule<Cart, Cart>();
   
  //DI Container sẽ tự inject Database, Logger vào Cart
  var myCart = DIContainer.GetModule();
   
  //Khi cần thay đổi, ta chỉ cần sửa code define
  DIContainer.SetModule<IDatabase, XMLDatabase>();
view raw DIAfter.cs hosted with ❤ by GitHub


Trên thực tế ta sẽ sử dụng các thư viện hoặc frameworks để cài đặt DI container cho project của mình (Tất nhiên ta có thể tự implement container cho riêng mình, nhưng không nên).


Các DI Container cho C# phải kể đến như

  • Microsoft.Extensions.DependencyInjection
  • Unity
  • StructureMap
  • Castle Windsor
  • Ninject
  • Autofac
  • DryIoc
  • Simple Injector
  • Light Inject

Theo cá nhân mình thì nên dùng Microsoft.Extensions.DependencyInjection. Cái này chính chủ của Microsoft được dùng mặc định trên ASP .NET Core. Tuy nhiên bạn có thể đặt cho các loại project khác nhau dùng C#.

Kết luận
Việc áp dụng DI sẽ có nhưng ưu điểm sau
  • 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ưng bên cạnh đó cũng có những nhược điểm
  • Khái niệm DI khá “khó tiêu”, các developer mới sẽ gặp khó khăn khi học
  • 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ác object được khởi tạo toàn bộ ngay từ đầu, có thể làm giảm performance
  • Làm tăng độ phức tạp của code

Nguồn: dotnetcoban

TIGO DESK

 Phone: +043 998 2218

 Fax: +0432 24 24 02

 Giấy phép ĐKKD: 0109122140

 Email: info@tigosolutions.com hoặc tigosoftware@gmail.com

 Online support: m.me/tigogroup


Tư vấn và hỗ trợ Tư vấn + 

CRM, e-Marketing, Portal, ERP, Accounting, HelpDesk, Service Desk, Dashboard, B2B, B2C,giải pháp quản trị doanh nghiệp, cổng thông tin, chăm sóc khách hàng,dịch vụ phần mềm chuyên nghiệp, dịch vụ thiết kế Web chuyên nghiệp, phần mềm thông minh,phần mềm tiện ích,freelancer,phần mềm POS, phần mềm quản lý bán hàng, phần mềm quản lý văn phòng, phần mềm booking, thiết kế website, thiết kế landing page, phần mềm thu chi, email marketing, quảng cáo trực tuyến, google adwords, online marketing