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!