terminal

codeando_simple

terminal

menu

terminal

search_module

guest@codeandosimple: ~/system/search $ grep -r "" .

Press [ENTER] to execute search

Status

Engine: Ready

Database: Online

Index: V2.1.0_LATEST

bash -- cat dependency-inversion.md
guest@codeandosimple: ~/blog/solid $ cat dependency-inversion.md

SOLID - DEPENDENCY INVERSION PRINCIPLE_

// "Energy and persistence conquer all things" - Benjamin Franklin

The Dependency Inversion Principle (DIP) states that:

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend on details. Details should depend on abstractions.
play_circle

Play explanatory video

In simpler terms... High-level classes (those that contain the business logic) should not depend on low-level classes (those that handle specific details like the database or sending emails). Both should depend on interfaces or abstract classes.

# Inversion, Abstractions, and Details?

Inversion: Changing the traditional direction of dependencies in object-oriented programming.

Abstractions: Interfaces or abstract classes that define behavior contracts.

Details: Specific and concrete parts of a system's implementation, such as Database, User Interface components.

The Dependency Inversion Principle aims to reduce coupling between high-level code (policies, decisions) and low-level code (details, implementations), leading to more flexible and maintainable systems.

# Why is it important?

Flexibility

Facilitates changing implementation details without affecting high-level code.

Reusability

High-level modules become more reusable by not being tied to specific implementations.

Maintainability

Improves maintainability by decreasing coupling between different parts of the code.

# Symptoms of violation

We can identify when we are not respecting the Dependency Inversion Principle:

  • High-level code is directly linked to specific low-level implementations.
  • Changes in low-level implementation details affecting high-level code.
  • Difficulty changing or swapping low-level components due to rigid dependencies.

Example

Let's imagine a notification system for an application.

Without Dependency Inversion Principle

The notification system is directly coupled to a specific messaging implementation.

public class NotificationService {
    private EmailService emailService;
    public NotificationService() {
        this.emailService = new EmailService(); // Direct coupling
    }
    public void sendNotification(User user, String message) {
        emailService.sendEmail(user, message);
    }
    }

Switching to another notification method would require modifying NotificationService.

With Dependency Inversion Principle

We define an abstraction for message sending and make NotificationService depend on this abstraction.

public interface MessageService {
    void sendMessage(User user, String message);
    }
public class EmailService implements MessageService {
    sendMessage(User user, String message) {
        // Specific implementation for sending an email
    }
    }
public class NotificationService {
    private MessageService messageService;
    public NotificationService(MessageService service) {
        // Dependency on an abstraction
    }
    public void sendNotification(User user, String message) {
        messageService.sendMessage(user, message);
    }
    }

Conclusions

By implementing the Dependency Inversion Principle (DIP), NotificationService does not depend directly on EmailService but on the MessageService abstraction.

This means we can change how messages are sent (for example, to SMS or push notifications) without having to modify NotificationService.

Summary

The Dependency Inversion Principle is vital for creating flexible and decoupled systems. It encourages high-level modules to not depend on low-level ones, but on abstractions, facilitating future changes and improving maintainability. DIP promotes a robust software design, where modifications to implementation details have a minimal impact on high-level code, making the system more adaptable and easier to evolve.