Dependency Inversion Principle

The Dependency Inversion Principle (DIP) says that high-level modules should not depend on low-level modules, but should depend on abstractions.

DIP encourages the decoupling of components in a system, making it more flexible and easier to maintain.

DIP violation example

Consider a scenario where the User class is responsible for both user data and notifying users through email.

class EmailService {
  sendEmail(message: string) {...}
}

class User {
  private emailService: EmailService;

  constructor() {
    this.emailService = new EmailService();
  }

  notify(message: string) {
    this.emailService.sendEmail(message);
  }
}

The User class directly depends on the EmailService class. This tight coupling makes it difficult to switch to other notification methods, such as SMS or push notifications, without modifying the User class.

Introduce an abstraction

We can create an interface for the notification service and have different notification classes implement this interface.

interface NotificationService {
  notify(message: string): void;
}

class EmailService implements NotificationService {
  notify(message: string) {...}
}

class SMSService implements NotificationService {
  notify(message: string) {...}
}

class User {
  private notificationService: NotificationService;

  constructor(notificationService: NotificationService) {
    this.notificationService = notificationService;
  }

  notify(message: string) {
    this.notificationService.notify(message);
  }
}

By introducing the NotificationService interface, the User class now depends on this abstraction rather than a concrete implementation.

This decoupling allows us to easily switch between different notification services (like EmailService or SMSService) without modifying the User class. It improves flexibility, maintainability, and testability.


Find this post helpful? Subscribe and get notified when I post something new!