In the previous lesson, we saw how a tightly coupled design violates the Dependency Inversion Principle (DIP). To follow DIP, we need to introduce abstractions and make both high-level and low-level modules depend on them, rather than depending directly on each other.
Step 1: Introduce an Abstraction
We’ll start by creating an abstraction for sending notifications. This abstraction will be an interface called NotificationSender, which defines a method for sending notifications.
Step 2: Implement Low-Level Modules Based on the Abstraction
Next, we’ll implement different notification services, such as EmailService and SMSService, that depend on the NotificationSender interface.
Now, the low-level modules (EmailService and SMSService) implement the NotificationSender interface, following the principle that details should depend on abstractions.
Step 3: Modify the High-Level Module to Depend on the Abstraction
We’ll refactor the NotificationService to depend on the NotificationSender interface instead of directly on a specific implementation. This will decouple the high-level module from the low-level details.
Step 4: Test the Refactored Design
Now, we can easily switch between different implementations of NotificationSender (e.g., EmailService and SMSService) without modifying the NotificationService.
Explanation
- NotificationSender Interface: Defines a general
send()method for sending notifications. - EmailService and SMSService Classes: Implement the
NotificationSenderinterface, providing specific implementations for sending emails and SMS. - NotificationService Class: Depends on the
NotificationSenderinterface, making it flexible and decoupled from specific implementations. - Main Class: Demonstrates using the
NotificationServicewith bothEmailServiceandSMSService.
How This Solution Follows DIP
- High-Level Module Depends on Abstraction: The
NotificationServiceclass depends on theNotificationSenderinterface, not on specific implementations. - Low-Level Modules Depend on Abstraction: Both
EmailServiceandSMSServiceimplement theNotificationSenderinterface, allowing them to follow a common contract. - Flexible and Extensible: New notification types can be added by implementing the
NotificationSenderinterface without modifying the existingNotificationService.