DEV Community

Cover image for SOLID: D - Dependency Inversion Principle (DIP)
Paulo Messias
Paulo Messias

Posted on

2

SOLID: D - Dependency Inversion Principle (DIP)

Introduction to DIP:
The Dependency Inversion Principle (DIP) is the final principle in the SOLID design principles. DIP states that high-level modules should not depend on low-level modules. Both should depend on abstractions (e.g., interfaces). Additionally, abstractions should not depend on details; details should depend on abstractions. This principle is essential for creating systems that are modular, flexible, and easy to maintain.

Objectives of DIP:

  • Promote Loose Coupling: Reduces the dependencies between high-level and low-level modules, making the system more flexible.
  • Enhance Modularity: Encourages the use of interfaces and abstractions, which facilitates the replacement of components without affecting the entire system.
  • Improve Testability: By depending on abstractions, modules become easier to mock or stub during testing.
  • Support Reusability: Modules that depend on abstractions can be reused in different contexts without modification.

Bad Practice Example (Classes):
Here we have a LightBulb class that directly depends on a Switch class, creating tight coupling.

class LightBulb {
  turnOn(): void {
    console.log("LightBulb is turned on");
  }

  turnOff(): void {
    console.log("LightBulb is turned off");
  }
}

class Switch {
  lightBulb: LightBulb;

  constructor(lightBulb: LightBulb) {
    this.lightBulb = lightBulb;
  }

  operate(): void {
    this.lightBulb.turnOn();
  }
}

const lightBulb = new LightBulb();
const mySwitch = new Switch(lightBulb);
mySwitch.operate();
Enter fullscreen mode Exit fullscreen mode

In this approach, the Switch class is tightly coupled to the LightBulb class, violating DIP.

Good Practice Example (Classes):
To follow DIP, we can introduce an abstraction (e.g., Switchable) that both classes depend on.

interface Switchable {
  turnOn(): void;
  turnOff(): void;
}

class LightBulb implements Switchable {
  turnOn(): void {
    console.log("LightBulb is turned on");
  }

  turnOff(): void {
    console.log("LightBulb is turned off");
  }
}

class Switch {
  device: Switchable;

  constructor(device: Switchable) {
    this.device = device;
  }

  operate(): void {
    this.device.turnOn();
  }
}

const lightBulb = new LightBulb();
const mySwitch = new Switch(lightBulb);
mySwitch.operate();
Enter fullscreen mode Exit fullscreen mode

In this approach, the Switch class depends on the Switchable interface, making it independent of the concrete LightBulb implementation.

Bad Practice Example (Functions):
Here’s an example where a function directly depends on a low-level module.

class DatabaseConnection {
  connect(): void {
    console.log("Connected to the database");
  }
}

class UserService {
  database: DatabaseConnection;

  constructor() {
    this.database = new DatabaseConnection();
  }

  saveUser(): void {
    this.database.connect();
    console.log("User saved");
  }
}
Enter fullscreen mode Exit fullscreen mode

In this approach, the UserService class is directly dependent on the DatabaseConnection class, violating DIP.

Good Practice Example (Functions):
To follow DIP, we can introduce an abstraction that the UserService class depends on.

interface Database {
  connect(): void;
}

class DatabaseConnection implements Database {
  connect(): void {
    console.log("Connected to the database");
  }
}

class UserService {
  database: Database;

  constructor(database: Database) {
    this.database = database;
  }

  saveUser(): void {
    this.database.connect();
    console.log("User saved");
  }
}

const databaseConnection = new DatabaseConnection();
const userService = new UserService(databaseConnection);
userService.saveUser();
Enter fullscreen mode Exit fullscreen mode

In this approach, the UserService class depends on the Database interface, making it more flexible and testable.

Conclusion:
The Dependency Inversion Principle is crucial for creating flexible and maintainable systems. By ensuring that high-level modules do not directly depend on low-level modules, but instead rely on abstractions, we can create systems that are more modular, easier to test, and more resilient to change.

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more →

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