bài viết ngẫu nhiên
Định nghĩa ngắn gọn:
👉 Module cấp cao không nên phụ thuộc vào module cấp thấp. Cả hai nên phụ thuộc vào abstraction.
Nói đơn giản: thay vì để UserService “ôm chặt” MySQL, hãy để nó “ôm” một interface Database. MySQL, PostgreSQL, hay MongoDB chỉ việc đứng sau interface đó mà phục vụ thôi.
class MySQLDatabase {
save(data: string) {
console.log("Saving to MySQL:", data);
}
}
class UserService {
private db = new MySQLDatabase();
addUser(user: string) {
this.db.save(user);
}
}
Ở đây:
interface Database {
save(data: string): void;
}
class MySQLDatabase implements Database {
save(data: string) {
console.log("Saving to MySQL:", data);
}
}
class PostgreSQLDatabase implements Database {
save(data: string) {
console.log("Saving to PostgreSQL:", data);
}
}
class UserService {
constructor(private db: Database) {}
addUser(user: string) {
this.db.save(user);
}
}
NestJS vốn đã áp dụng DIP một cách tự nhiên nhờ Dependency Injection (DI).
@Injectable()
class UserService {
constructor(private readonly db: Database) {}
addUser(user: string) {
this.db.save(user);
}
}
Trong AppModule, bạn chỉ cần bind interface với implementation:
@Module({
providers: [
UserService,
{ provide: 'Database', useClass: MySQLDatabase },
],
})
export class AppModule {}
Muốn đổi sang PostgreSQL? Chỉ việc thay useClass. Code business không hề động tới.
DIP rất hữu ích, nhưng nếu project của bạn:
Thì việc tạo thêm abstraction có thể làm code “phức tạp hóa vấn đề”.
Hãy nhớ: nguyên tắc chỉ là công cụ, không phải luật bất di bất dịch.
Dependency Inversion Principle giúp code của bạn bớt “dính chặt”, linh hoạt và dễ bảo trì hơn.
Hãy tưởng tượng abstraction là “ổ cắm điện”, còn implementation là các loại “phích cắm”.
Miễn bạn cắm vào đúng ổ, thì đó là bàn là, tủ lạnh, hay sạc điện thoại đều chạy được 🔌.
Lần sau khi viết một class, hãy tự hỏi:
Nếu câu trả lời là “có thể đổi thoải mái”, thì xin chúc mừng: bạn đã đi đúng hướng DIP rồi! 🎉
Bạn có muốn mình viết thêm demo code Unit Test với mock database để minh họa lợi ích DIP trong testing không?