DEV Community

vinaykumar0339
vinaykumar0339

Posted on

#5 Dependency Inversion Principle ['D' in SOLID]

DIP - Dependency Inversion principle
The Dependency Inversion Principle is the fifth principle in the Solid Design Principles.

  1. High-Level Modules should not depend on Low-Level Modules. Both Should depend on abstraction.
  2. Abstraction should not depend on details. Details should depend on Abstraction.

High-Level Modules.

  1. The High-Level Module contains important policy decisions and business logic.
  2. They Should not be affected by changes in Low-Level Modules.

Low-Level Modules.

  1. Low-Level Modules contains implementation details.
  2. Changes in Low-Level Modules should not affect High-Level Modules.

Abstractions

  1. Use Interfaces or Abstraction class to create an abstraction Layer.
  2. High-level modules depend on this abstraction, allowing low-level module changes without affecting high-level logic.

Violating DIP:

class FileLogger {
    func log(message: String) {
        print("Loggin Message to file: \(message)")
    }
}

class UserService {
    private let logger = FileLogger()

    func createUser(name: String) {
        logger.log(message: "User \(name) Created.")
    }
}

// usage
let userService = UserService()
userService.createUser(name: "Vinay Kumar")
Enter fullscreen mode Exit fullscreen mode

Issues with Violating DIP:

  1. Tight Coupling:
    • UserService is tightly coupled to FileLogger, making it difficult to change the logging mechanism without modifying UserService.
  2. Reduced Flexibility:
    • Switching to a different logging method (e.g., database logger, cloud logger) requires changing UserService.
  3. Hard to Test:
    • Testing UserService requires an actual FileLogger, making it harder to isolate and test.

Adhering to DIP:
Using Protocol We can adhere to DIP.

protocol Logger {
    func log(message: String)
}

class FileLoggerDIP: Logger {
    func log(message: String) {
        print("Logging message to file: \(message)")
    }
}

class ConsoleLogger: Logger {
    func log(message: String) {
        print("Logging message to console: \(message)")
    }
}

class UserServiceDIP {
    let logger: Logger

    init(logger: Logger) {
        self.logger = logger
    }

    func createUser(name: String) {
        self.logger.log(message: "User \(name) Created")
    }
}

// usage
let fileLoggerDIP = FileLoggerDIP()
let consoleLogger = ConsoleLogger()
let userServiceDIPUsingFileLogger = UserServiceDIP(logger: fileLoggerDIP)
let userServiceDIPUsingConsoleLogger = UserServiceDIP(logger: consoleLogger)

userServiceDIPUsingFileLogger.createUser(name: "Vinay Kumar DIP FileLogger")
userServiceDIPUsingConsoleLogger.createUser(name: "Vinay Kumar DIP ConsoleLogger")
Enter fullscreen mode Exit fullscreen mode

Benefits of Adhering to DIP:

  1. Improved Maintainability:
    • Changes in low-level modules (like FileLogger) do not affect high-level modules (like UserService).
  2. Enhanced Flexibility:
    • Easily switch between different logging mechanisms without changing the high-level module.
  3. Increased Testability:
    • High-level modules can be tested with mock implementations of abstractions.

Drawbacks of Adhering to DIP:

  1. Increased Complexity:
    • Requires creating additional interfaces or abstract classes.
  2. More Boilerplate Code:
    • More code is needed to set up the abstractions.

Mitigating Drawbacks:

  1. Balanced Approach:
    • Apply DIP where it provides significant benefits, balancing simplicity and extensibility.
  2. Clear Documentation:
    • Maintain clear documentation to help developers understand the dependencies.
  3. Use of Dependency Injection Frameworks:
    • Use frameworks to manage dependencies efficiently.

Conclusion:
You can create more maintainable, understandable, and flexible software by thoughtfully understanding and applying the Dependency Inversion Principle. Ensuring that high-level modules depend on abstractions rather than low-level details promotes better software design and enhances the overall quality of the codebase.

Interface Segregation Principle
Single Responsibility Principle
Check My GitHub Swift Playground Repo.

Image of Datadog

Create and maintain end-to-end frontend tests

Learn best practices on creating frontend tests, testing on-premise apps, integrating tests into your CI/CD pipeline, and using Datadog’s testing tunnel.

Download The Guide

Top comments (0)

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up