
Dependency Injection (DI) là một nguyên tắc thiết kế phần mềm trong đó các đối tượng không tự tạo ra phụ thuộc của chúng, mà nhận phụ thuộc từ bên ngoài. Nhờ đó, các lớp trở nên độc lập, dễ kiểm thử và dễ thay thế. DI là nền tảng của Spring Framework, .NET Core và hầu hết framework hiện đại. Bài viết này giải thích vì sao DI quan trọng, phân biệt constructor injection với setter injection, và chỉ ra những sai lầm thường gặp.
Vấn đề: phụ thuộc cứng
Hãy xét một lớp OrderService cần gửi email thông báo. Cách viết tự nhiên — và sai — là tự khởi tạo lớp gửi mail bên trong:
public class OrderService {
private SmtpMailer mailer = new SmtpMailer("smtp.example.com");
public void placeOrder() {
mailer.send("Đơn hàng đã tạo");
}
}
Ở đây OrderService gắn chặt với SmtpMailer và với cấu hình SMTP cụ thể. Hệ quả:
- Không kiểm thử được. Muốn test
placeOrder(), bạn phải thật sự gửi email thật — hoặc lộ ra một hack rò rỉ phụ thuộc vào toàn cục. - Không thay thế được. Muốn đổi sang nhà cung cấp email khác, phải sửa mã nguồn lớp này.
- Không tái sử dụng được. Cứ mỗi lần tạo
OrderServicelại tạo một kết nối SMTP mới, tốn tài nguyên.

Giải pháp: tiêm phụ thuộc từ bên ngoài
Dependency Injection đảo ngược trách nhiệm: bên nhận (client) chỉ khai báo cần gì, còn bên cung cấp (service provider) quyết định dùng cái gì. Lớp OrderService được viết lại:
public class OrderService {
private readonly IMailer mailer;
public OrderService(IMailer mailer) {
this.mailer = mailer;
}
public void placeOrder() {
mailer.send("Đơn hàng đã tạo");
}
}
Giờ muốn test, chỉ cần truyền vào một FakeMailer ghi nhận nội dung email. Muốn đổi nhà cung cấp, chỉ thay ở nơi khởi tạo mà không đụng tới logic nghiệp vụ. Đây chính là ý nghĩa của “Inversion of Control” — quyền kiểm soát được đảo chiều.

Ba kiểu tiêm phụ thuội phổ biến
| Kiểu | Cách thực hiện | Đánh giá |
|---|---|---|
| Constructor injection | Truyền phụ thuộc qua tham số hàm khởi tạo | Khuyến nghị — phụ thuộc bắt buộc, rõ ràng, dễ test |
| Setter injection | Gán qua hàm setter | Dành cho phụ thuộc tuỳ chọn, đã khởi tạo xong mới set |
| Field injection | Gán trực tiếp vào thuộc tính (@Autowired) | Ngắn gọn nhưng ẩn phụ thuộc, khó unit test — nên tránh |
Quy tắc thực tế: constructor injection cho mọi phụ thuộc bắt buộc. Nó làm lớp bất biến (immutable), biểu thị đúng điều kiện tồn tại của đối tượng, và giúp trình biên dịch bắt lỗi sớm khi thiếu phụ thuộc.
IoC container: khái niệm và lựa chọn
Container (hay service locator, injector) là thành phần giữ vai trò bên cung cấp. Nó biết cách tạo đối tượng và tự động điều phụ thuộc. Các loại phổ biến:
- Bản chất có sẵn — Spring
@Component+ constructor injection; .NET Core built-in DI; Guice; Dagger (compile-time). - Convention over configuration —
@Service,@Repositorytrong Spring: container tự nhận diện lớp qua annotation. - Annotation-driven — phổ biến trong Java, C#.
Nếu dự án nhỏ, DI thủ công qua constructor thường là đủ và dễ theo dõi hơn — không cần framework nào. Container chỉ đáng dùng khi số lớp và mức độ phụ thuộc đủ lớn để quản lý thủ công trở nên cồng kềnh.
Những sai lầm thường gặp
- Service locator phủ nhận DI. Gọi
container.GetService(IMailer)bên trong lớp nghiệp vụ tạo phụ thuộc ẩn — đúng cái DI cố tránh. Phụ thuộc phải đi qua constructor. - Dependency Injection Framework (DIF) để ghi code. Dùng framework DI để xử lý luồng nghiệp vụ tạo mã khó hiểu, khó test. Nó chỉ nên dùng cho dữ liệu đơn giản, không có logic.
- Phụ thuộc cụ thể thay vì interface. Phụ thuộc vào lớp cụ thể khiến mất khả năng thay thế — đúng thứ DI sinh ra để loại bỏ.
- Lạm dụng singleton. Đặt mọi thứ là singleton để “tiện” dễ dẫn tới trạng thái chia sẻ và lỗi khó truy.

Kết luận
Dependency Injection không chỉ là kỹ thuật, mà là nguyên tắc thiết kế: đảo ngược hướng phụ thuộc để phần mềm dễ thay đổi, dễ kiểm thử, dễ mở rộng. Với constructor injection làm mặc định, bạn đã nắm được phần lớn lợi ích mà không cần framework phức tạp. Hãy áp dụng nó cho lớp nghiệp vụ trước, để framework DI chỉ là lớp trợ giúp hợp lý ở biên.
Tài nguyên để tìm hiểu thêm
Nếu bạn muốn đi sâu hơn, các tài liệu chính thức sau đây có ví dụ code đầy đủ bằng nhiều ngôn ngữ:
- Spring Guides — Constructor Injection: hướng dẫn chính thức của Spring về constructor injection.
- Microsoft Learn — Dependency injection in .NET: giải thích các lifetime (singleton, scoped, transient) của container .NET.
- Wikipedia — Dependency injection: lịch sử, gốc lý thuyết từ Fowler và inversion of control.
