Design Patterns Explained
  • Getting Started
  • SOLID Principles
    • Single Responsibility Principle
    • Open / Closed Principle
    • Liskov Substitution Principle
    • Interface Segregation Principle
    • Dependency Inversion Principle
  • Design Patterns
    • Creational Patterns
      • Abstract Factory Pattern
      • Builder Pattern
      • Factory Pattern
      • Prototype Pattern
      • Singleton Pattern
    • Behavioral Patterns
      • Chain Of Responsibility Pattern
      • Command Pattern
      • Interpreter Pattern
      • Iterator Pattern
      • Mediator Pattern
        • Example 1
      • Memento Pattern
      • Observer Pattern
      • State Pattern
      • Strategy Pattern
        • Example 1
      • Template Method Pattern
      • Visitor Pattern
    • Structural Patterns
      • Adapter Pattern
      • Bridge Pattern
      • Composite Pattern
      • Decorator Pattern
      • Facade Pattern
      • Flyweight Pattern
      • Proxy Pattern
  • Roadmap
Powered by GitBook
On this page
  • Refactoring to apply DIP
  • Example
  • Solution

Was this helpful?

  1. SOLID Principles

Dependency Inversion Principle

PreviousInterface Segregation PrincipleNextDesign Patterns

Last updated 4 years ago

Was this helpful?

Dependency Inversion Principle

Definition: High-level modules should not depend on Low-level Modules, but both should depend on abstractions.

The normal flow of module dependencies: High-level Modules -> Medium Level Modules -> Low-level Modules.

Normal Flow of Dependencies

What we want to achieve: High-level Modules -> Medium Level Abstractions <- Medium Level Modules -> Low-level Abstractions <- Low-level Modules.

  • DI Principle helps in achieving a very decoupled code that helps in extension and testing.

  • To achieve true dependency inversion, every dependency should be explicit (no new keywords inside the module).

  • To achieve explicit dependencies we can use dependency injection either in Constructors, Method Parameters, property injection, or even using IoC Containers (Inversion of Control).

Refactoring to apply DIP

  1. Extract Dependencies into interfaces

  2. Inject Implementations of interfaces

  3. Apply SRP

Example

class UserService {
  public registerUser(user: User) {
    // registration logic

    const notificationService = new NotificationService();
    notificationService.notifyUser(user, 'Welcome');
  }
}

class NotificationService {
  public notifyUser(user: User, message: string) {
    // notification logic
  }
}

The problem here is actually obvious, the UserService is coupled with this exact NotificationService, testing the UserService alone is impossible, and of course, it became much harder to run tests as NotificationService may rely on things not available in the current time (a special mail server or something).

One solution is to make the NotificationService an explicit dependency, but don't forget that polymorphism will not work, as NotificationService is a concrete class not an abstraction.

Solution

class UserService {
  private _notificationService;

  public constructor(notificationService: INotificationService) {
    this._notificationService = notificationService;
  }

  public registerUser(user: User) {
    // registration logic

    this._notificationService.notifyUser(user, 'Welcome');
  }
}

interface INotificationService {
  notifyUser(user: User, message: string): void;
}

class NotificationService implements INotificationService {
  public notifyUser(user: User, message: string) {
    // notification logic
  }
}

Now as the dependency with the NotificationService is decoupled and relies on an abstraction, the NotificationService implementation can be swapped in runtime with other concrete classes that implement the same abstraction.

Also now writing Unit tests for UserService doesn't rely on the NotificationService anymore as a mock can be written to replace the real NotificationService during the tests.

Dependency Inversion Flow
Example UML
Solution UML