SOLID Principles in 59 Seconds

D — Dependency Inversion Principle

5 of 5
The principle

High-level modules should not depend on low-level modules; both should depend on abstractions.

High-level code decides what should happen; low-level code handles how it happens. Both depend on a shared interface, so the high-level code does not need to know a particular implementation.

NotificationDispatcher creates its own EmailSender. Using SMS or a test sender would require editing the dispatcher.

Shared contract
Orders service
Delivery services
OrderShipped eventApplication-owned contract
Services agree on the event format. Provider details stay in the delivery services.
Language

Original code

class NotificationDispatcher {
  private sender = new EmailSender()

  send(message: string) {
    this.sender.send(message)
  }
}

Revised code

interface Sender {
  send(message: string): void
}

class NotificationDispatcher {
  private sender: Sender

  constructor(sender: Sender) {
    this.sender = sender
  }

  send(message: string) {
    this.sender.send(message)
  }
}

const dispatcher = new NotificationDispatcher(new EmailSender())
Review

The five questions

  • S Does this class handle responsibilities that can change independently?
  • O Can I extend functionality without editing existing code?
  • L Would the caller still understand the result if I replaced one sender with another?
  • I Does a client depend on interface methods it never uses?
  • D Can the implementation change without editing the high-level code?

Sources: Martin on Single Responsibility, Robert C. Martin’s design principles, and Liskov and Wing on behavioral subtyping.