The Dependency Inversion Principle (DIP) says that high-level modules should not depend on low-level modules, but should depend on abstractions.
DIP encourages the decoupling of components in a system, making it more flexible and easier to maintain.
Consider a scenario where the User class is responsible for both user data and
notifying users through email.
class EmailService {
sendEmail(message: string) {...}
}
class User {
private emailService: EmailService;
constructor() {
this.emailService = new EmailService();
}
notify(message: string) {
this.emailService.sendEmail(message);
}
}
The User class directly depends on the EmailService class. This tight
coupling makes it difficult to switch to other notification methods, such as SMS
or push notifications, without modifying the User class.
We can create an interface for the notification service and have different notification classes implement this interface.
interface NotificationService {
notify(message: string): void;
}
class EmailService implements NotificationService {
notify(message: string) {...}
}
class SMSService implements NotificationService {
notify(message: string) {...}
}
class User {
private notificationService: NotificationService;
constructor(notificationService: NotificationService) {
this.notificationService = notificationService;
}
notify(message: string) {
this.notificationService.notify(message);
}
}
By introducing the NotificationService interface, the User class now depends
on this abstraction rather than a concrete implementation.
This decoupling allows us to easily switch between different notification
services (like EmailService or SMSService) without modifying the User
class. It improves flexibility, maintainability, and testability.
Find this post helpful? Subscribe and get notified when I post something new!