Chúng ta sẽ tìm hiểu về khái niệm Dependency Injection (DI), Inversion of Control (IoC), tầm quan trọng và cách sử dụng chúng trong môi trường lập trình Xamarin với Autofac container.

Dependency Inversion

Ngôn ngữ lập trình chủ yếu trên Xamarin là C#, một ngôn ngữ hướng đối tượng. Mà nhắc đến phương pháp lập trình hướng đối tượng OOP thì không thể nào không nhắc đến nguyên lý SOLID trong thiết kế chương trình:

Bạn có thể đọc thêm về SOLID ở đây

Trong ó nguyên lý D (Dependency Inversion Principle) trong SOLID được phát biểu như sau:

  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.)

Chúng ta sẽ rõ với các ví dụ sau:

Ta có class HttpService chứa các phương thức truy cập remote API và HttpService được sử dụng trong PlaylistViewModel

Chúng ta thấy  class PlaylistViewModel phụ thuộc vào HttpService và HttpService lại phụ thuộc vào HttpClient (System.Net.Http), ở đây đã vi phạm nguyên lý trong SOLID, vì giả sử project mình target đến một platform không có hỗ trợ System.Net.Http.HttpClient thì lúc này mọi thứ sẽ “vỡ”.

Một ví dụ vi phạm khác, chúng ta cần sự dụng tính năng chụp ảnh của device để chụp và lưu ảnh avatar chẳng hạn

Nếu project mình target trên Android và cả iOS, thì nội dung phương thức TakePhotoAsync phải sử dụng platform native code để viết, nghĩa là có tới 2 trường hợp implement khác nhau của phương thức TakePhotoAsync.

Để giải quyết 2 vấn đề trên ta áp dụng nguyên lý D bằng cách thiết kế dạng interface cho HttpService và MediaService

Sau đó tạo các implementation của chúng

Và sử dụng chúng thay vì sự dụng implementation:

Dependency Injection

Theo ví dụ trên, vấn đề còn lại ở đây là làm sau resolve đối tượng implementation tương ứng khi sử dụng interface của chúng? Ví dụ trường hợp IMediaService ta thấy Android/iOSMediaService đều là platform code (nằm ở tầng platform), các ViewModel thì ở tần Shared, và implementation phải được resolve theo runtime platform tương ứng.

Lúc này ta cần một bộ Container để binding và resolve các interface và implementation của chúng, phương pháp resolve này gọi là <strong>Dependency Injection</strong> (DI) và các container hỗ trợ DI thường gọi là <strong>DI Container</strong>.

Phân loại: Tùy vào sự hỗ trợ của bộ container mà ta có các dạng injection khác nhau, trong ó 2 dạng injection phổ biến của DI

  1. Constructor Injection: Các dependency sẽ được container truyền (inject) vào 1 class thông qua constructor của class đó.
  2. Property Injection: Các dependency sẽ được truyền vào 1 class thông qua các hàm setter/getter của các thuộc tính.

Với vị dụ ở trên, Constructor Injection có thể được viết như sau:

Ưu điểm của DI

  1. Late binding (ví dụ về bind ở IMediaService, một interface nhưng có 2 cách implement và nó sẽ đi theo runtime platform tương ứng)
  2. Khả năng mở rộng
  3. Dễ bảo trì, thay thế
  4. Dễ test và viết UnitTest
  5. Code được cấu trúc rõ ràng hơn

Nhược điểm của DI

  1. Khó khăn khi tiếp xúc (do chứa đầy đủ tính chất của lập trình OOP)
  2. Code ‘phức tạp’ thêm ở một số nơi
  3. Quá ‘lớn’ đối với project ‘nhỏ’
  4. Gây khó cho qua trình debug
  5. Đặt ra vấn đề kiểm soát Performance

Inversion of Control

Inversion of Control (IoC) cũng là một nguyên lý trong thiết kế hướng đối tượng (design principle, tuy nhiều người vẫn xem nó là một design pattern). Nó đảo ngược (thay đổi) các phương thức điều khiển trong mô hình thiết kế hướng đối tượng truyền thống, để giảm sự liên kết hoặc phụ thuộc ở các module, ví dụ thay đổi việc điều khiển luồng của một chương trình, thay đổi việc khởi tạo các đối tượng hoặc việc khởi tạo cũng như binding giữa các module phụ thuộc.

Ta có thể hiểu nguyên lý IoC qua ví dụ: Giả sử thông thường bạn tự lái (điều khiển) xe để đến công ty. Nếu tiến trình này có apply IoC thì IoC sẽ giúp tiến trình này có thể đảo lại thành bạn thuê taxi để đưa bạn đến công ty, ta thấy input là bạn, output là đến công ty, 2 trường hợp đều có kết quả giống nhau. Ở trường hợp 2 thì bạn có thể làm việc khác trong lúc tài xế taxi là người lái (điều khiển) chính.

Vì IoC chỉ là nguyên lý thiết kê, nó không có cung cấp phương pháp triển khai cụ thể, trong lập trình hướng đối tược IoC sẽ được hỗ trợ bởi một số phương thức cụ thể như:

  • Factory
  • Service Locator
  • Events
  • Delegates
  • DI

IoC Container

Tổng hợp lại ta có các bộ thư viện hỗ trợ IoC, gọi chung là IoC Container, đa số IoC Container đều hỗ trợ DI nên nó cũng là một DI Container.

Một IoC container sẽ có các nhiệm vụ sau:

  1. Register: Đăng ký các dependency ví dụ như đăng ký các class cấp cao hoặc binding các interface với implementation của chúng
  2. Resolve: Khi sử dụng IoC thì các class cấp cao chúng ta sẽ không tạo chúng theo các thông thường (thông qua phương thức new). Lúc này mọi việc sẽ do IoC container giải quyết, IoC sẽ tìm theo các binding và cấu hình lúc register của mình để tạo ra các đối tượng phụ hợp
  3. Dispose: IoC Container sẽ chịu trách nhiệm quản lý thời gian “sống” (lifetime) của mỗi object nó resolve ra, ví dụ như trong một scope, trong cả chương trình hoặc mỗi lần resolve…

Có nhiều container, opensource hỗ trợ .NET như:

Autofac

Autofac là một thư viện IoC khá phổ biến và mạnh mẽ cho môi trường lập trình .NET, hỗ trợ hầu hết các nền tảng như ASP.NET, .NET Core, Xamarin, UWP, …

Autofac có thể cài đặt vào dự án thông qua nuget: https://www.nuget.org/packages?q=Owner%3A%22alexmg%22+Autofac*. Đối với một dự án Xamarin Forms bạn sẽ phải cài Autofac cho project shared (hỗ trợ pcl và .net standard) lẫn các project platform Android, iOS.

Với các ví dụ trên ta có thể giao cho Autofac xử lý như sau

Tương tự cho việc Register class MediaService, chỉ khác là phần builder sẽ mở rộng cho tần platform có thể sử dụng để register trước khi build thành container.

Scope và Lifetime Ngoài việc register/resolve các components thì Autofac cũng hỗ trợ chúng ta kiểm soát phạm vi hoạt động, trình tự cấp phát cũng như giải phóng (dispose) các component. Có một số scope phổ biến như

  • Instance Per Dependency: (mặc định), mỗi lần resolve sẽ tạo 1 instance mới
  • Single Instance: sử dụng duy nhất một instance
  • Instance Per Lifetime Scope: trong một lifetime scope sẽ có 1 instance duy nhất
  • Instance Per Request: (hỗ trợ ASP.NET MVC, ASP.NET Core) sẽ 1 instance cho riêng một lần request

Xem thêm về Lifetime Scope và các loại Instance Scope ở đây: https://autofaccn.readthedocs.io/en/latest/lifetime/