Dependency Injection (DI) là pattern cung cấp dependency cho object từ bên ngoài thay vì object tự tạo (new). Giảm coupling, tăng testability, maintainability.
4 Loại DI chính
1. Constructor Injection (Khuyến nghị nhất)
public class OrderService
{
private readonly IRepository _repo;
private readonly ILogger _logger;
public OrderService(IRepository repo, ILogger logger) // Required dependencies
{
_repo = repo ?? throw new ArgumentNullException(nameof(repo));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
}
Ưu: Bắt buộc, bất biến, rõ ràng dependencies, dễ test, hỗ trợ DI container tự động.
Nhược: Constructor quá dài nếu nhiều dependency (code smell -> tách class).
2. Setter/Property Injection
public class ReportGenerator
{
public IExporter Exporter { get; set; } // Optional dependency
public void Generate() => Exporter?.Export();
}
Ưu: Optional, có thể thay đổi runtime, hỗ trợ serialization/UI framework.
Nhược: Temporal coupling (gọi method trước khi set -> NullRef), ẩn dependency.
3. Interface Injection
public interface ILoggerSetter { void SetLogger(ILogger logger); }
public class Service : ILoggerSetter
{
private ILogger _logger;
public void SetLogger(ILogger logger) => _logger = logger;
}
Ưu: Ép consumer implement interface setter rõ ràng.
Nhược: Phức tạp, ít dùng, coupling với interface setter.
4. Service Locator (Anti-pattern theo nhiều người)
public class LegacyController
{
public void Action()
{
var service = ServiceLocator.Current.GetService<IService>(); // Lookup
service.DoWork();
}
}
Nhược: Ẩn dependency (không thấy trong constructor), khó test, coupling với container, vi phạm DIP.
So sánh tổng quan
| Loại | Bắt buộc? | Thay đổi runtime? | Dễ test? | Khuyến nghị |
|---|---|---|---|---|
| Constructor | Có | Khó | Rất dễ | ⭐⭐⭐⭐⭐ Mặc định |
| Property/Setter | Không | Dễ | Trung bình | Optional dependencies |
| Interface Injection | Có (qua interface) | Dễ | Trung bình | Hiếm dùng |
| Service Locator | Không (ẩn) | Dễ | Khó | Tránh (legacy only) |
Lợi ích chung của DI
- Giảm coupling: Class không biết concrete implementation.
- Tăng reusability: Dễ swap implementation.
- Cải thiện maintainability: Thay đổi implementation không ảnh hưởng consumer.
- Cải thiện testing: Dễ mock/stub dependency.
- Centralized configuration: Đăng ký dependency ở một nơi (Composition Root).
Nhược điểm chung
- Phức tạp hóa object graph (quá nhiều interface, class nhỏ).
- Khó debug (stack trace qua container).
- Performance overhead nhẹ (container resolution).
- Circular dependency dễ xảy ra nếu không cẩn thận.
Best Practice trong .NET Core
// Program.cs / Startup.cs
builder.Services.AddScoped<IRepository, SqlRepository>();
builder.Services.AddTransient<IEmailService, SmtpEmailService>();
builder.Services.AddSingleton<ICache, RedisCache>();
// Controller - Constructor Injection
public class OrdersController : ControllerBase
{
private readonly IRepository _repo;
public OrdersController(IRepository repo) => _repo = repo;
}