Hiểu rõ IoC và Dependency Injection qua ví dụ thực tế – Dễ hiểu cho mọi developer
Bài học từ người Thầy
Chúng tôi từng có một người Thầy đáng kính – người đã truyền đạt những khái niệm tưởng chừng như phức tạp bằng một cách đơn giản đến không ngờ. Tôi, như bao học trò khác, đã thấm nhuần cách truyền đạt đó. Một trong những chủ đề khiến tôi tâm đắc nhất chính là IoC – Inversion of Control – và cách triển khai của nó thông qua Dependency Injection.
IoC là gì và tại sao cần?
Nếu bạn xuất thân từ Java, Go hoặc NestJS thì có lẽ khái niệm IoC đã không còn xa lạ. Trong tiếng Việt, IoC được hiểu là Đảo ngược quyền kiểm soát. Thay vì để một đối tượng chủ động tạo và kiểm soát các phụ thuộc, chúng ta đảo ngược lại quá trình đó – để một hệ thống bên ngoài chịu trách nhiệm cung cấp các phụ thuộc đó.
Ví dụ: Không dùng IoC
Giả sử bạn đang viết một ứng dụng gửi thông báo. Bạn có một lớp tên là NotificationService
phụ thuộc trực tiếp vào lớp EmailService
. Khi đó, nếu bạn muốn thay thế bằng SMS hay push notification thì sẽ phải sửa code bên trong NotificationService
.
class EmailService { send(message) { console.log("Sending email:", message); } } class NotificationService { constructor() { this.emailService = new EmailService(); } notify(message) { this.emailService.send(message); } } const service = new NotificationService(); service.notify("Hello world!");
Như bạn thấy, NotificationService
phụ thuộc cứng vào EmailService
. Đây là một dạng tight coupling khiến code khó mở rộng.
Dependency Injection giúp giải quyết vấn đề
Giờ hãy áp dụng DI (Dependency Injection). Ta không để NotificationService
tạo EmailService
nữa, mà đưa vào từ bên ngoài.
class EmailService { send(message) { console.log("Sending email:", message); } } class SMSService { send(message) { console.log("Sending SMS:", message); } } class NotificationService { constructor(sender) { this.sender = sender; } notify(message) { this.sender.send(message); } } const sms = new SMSService(); const notification = new NotificationService(sms); notification.notify("New update available!");
Giờ đây, NotificationService
không còn phụ thuộc vào một loại dịch vụ cụ thể nữa. Bất kỳ lớp nào có phương thức send()
đều có thể được tiêm vào. Đó là sự linh hoạt mà DI mang lại.
Ứng dụng trong NestJS
NestJS là một framework tuyệt vời vì nó hỗ trợ sẵn cơ chế DI bằng decorator @Injectable()
và module system. Ta không cần phải tự quản lý các dependency nữa.
@Injectable() export class LoggerService { log(message: string) { console.log("[LOG]:", message); } } @Injectable() export class UserService { constructor(private logger: LoggerService) {} createUser(name: string) { this.logger.log("Creating user: " + name); } }
Khi UserService
được khởi tạo bởi NestJS, nó sẽ tự động tiêm LoggerService
vào constructor mà không cần bạn tạo thủ công. Đây là sức mạnh của IoC container trong NestJS.
Tổng kết
IoC và Dependency Injection giúp cho code trở nên mềm dẻo, dễ kiểm thử và dễ bảo trì. Nó giúp tách rời giữa phần logic và phần triển khai chi tiết. Nhờ đó, việc mở rộng và thay đổi trong hệ thống trở nên dễ dàng hơn bao giờ hết.
Bạn hãy thử áp dụng IoC và DI trong các dự án của mình để cảm nhận sự tiện lợi và "gọn gàng" mà nó mang lại nhé!
Tham gia cuộc trò chuyện