DEV Community

Vivek Kurmi
Vivek Kurmi

Posted on

Dependency Inversion Principle (DIP)

In the Dependency Inversion Principle (DIP), the key idea is to let something else create objects for you, and you use those objects without having to create them directly. You "invert" the control of object creation.

Imagine it like this: Instead of going to the store to buy groceries (creating objects directly), you order groceries online (letting someone else create and deliver them) and use them as needed.

In the same way, DIP encourages you to let a framework or other class create objects and inject (provide) them where you need them in your code. This makes your code more flexible and easier to change because you're not responsible for creating and managing everything yourself.

Let's explain DIP to a novice with an example where dependencies are injected manually via a configuration class:

Example (Dependency Injection with Manual Configuration):

Imagine you are building a simple notification system, and you want to send notifications via different channels such as email and SMS. Traditionally, you might create classes that directly use specific notification channels.

Violation of DIP (Without Dependency Injection):

class EmailNotification {
    public void sendEmail(String to, String message) {
        // Code to send an email
    }
}

class SMSNotification {
    public void sendSMS(String to, String message) {
        // Code to send an SMS
    }
}
Enter fullscreen mode Exit fullscreen mode

In this code, the EmailNotification and SMSNotification classes directly depend on specific notification channels, violating DIP because they tightly couple to low-level modules.

Now, let's adhere to DIP using manual dependency injection through a configuration class:

Adhering to DIP (With Dependency Injection via Configuration):

interface NotificationService {
    void sendNotification(String to, String message);
}

class EmailNotification implements NotificationService {
    public void sendNotification(String to, String message) {
        // Code to send an email
    }
}

class SMSNotification implements NotificationService {
    public void sendNotification(String to, String message) {
        // Code to send an SMS
    }
}

class NotificationApp {
    private NotificationService notificationService;

    public NotificationApp(NotificationService notificationService) {
        this.notificationService = notificationService;
    }

    public void sendNotification(String to, String message) {
        notificationService.sendNotification(to, message);
    }
}
Enter fullscreen mode Exit fullscreen mode

In this adhering-to-DIP example:

  • We define an interface NotificationService that defines the common behavior for sending notifications.
  • The EmailNotification and SMSNotification classes implement this interface, specifying how they send notifications.
  • The NotificationApp class depends on the NotificationService interface and can work with any class that implements this interface.

Configuration Class (Manual Dependency Injection):

Now, let's manually configure the dependencies using a configuration class:

public class AppConfig {
    public NotificationService emailNotificationService() {
        return new EmailNotification();
    }

    public NotificationService smsNotificationService() {
        return new SMSNotification();
    }

    public NotificationApp notificationApp() {
        return new NotificationApp(emailNotificationService());
    }
}
Enter fullscreen mode Exit fullscreen mode

In this AppConfig class:

  • We define two methods, emailNotificationService and smsNotificationService, which create instances of EmailNotification and SMSNotification.
  • We also define a method notificationApp that creates an instance of NotificationApp and injects the emailNotificationService into it.

This manual configuration allows us to inject dependencies into the NotificationApp class without changing its code. We can switch notification services by modifying the AppConfig class while adhering to the Dependency Inversion Principle.

Benefits of Manual Dependency Injection:

  • The NotificationApp class is no longer tightly coupled to specific notification channels, making it more flexible and maintainable.
  • We can easily switch between different notification services by modifying the configuration class, promoting adherence to DIP.

In summary, the Dependency Inversion Principle suggests that high-level modules should not depend on low-level modules. Instead, they should both depend on abstractions. Manual dependency injection, as shown in this example, helps achieve this separation of concerns and promotes flexibility in your code.

"Your feedback and ideas are invaluable โ€“ drop a comment, and let's make this even better!"

๐Ÿ˜ If you enjoy the content, please ๐Ÿ‘ like, ๐Ÿ”„ share, and ๐Ÿ‘ฃ follow for more updates!
Join me on a professional journey through my LinkedIn profile: Linkedin Profile

Top comments (0)