Принцип инверсии зависимостей гласит:

1️⃣ Модули верхнего уровня (бизнес-логика) не должны зависеть от модулей нижнего уровня (детали реализации, такие как конкретные базы данных, API или файловая система). Оба типа модулей должны зависеть от абстракций.   2️⃣ Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Простыми словами: ваш главный код должен управлять инструментами через интерфейсы, а не привязываться к конкретным классам жестко.


❌ Нарушение принципа (плохой код)

В этом примере класс бизнес-логики NotificationManager (верхний уровень) напрямую зависит от конкретного класса SmsService (нижний уровень). Если мы решим отправлять уведомления через Email, нам придется переписывать код NotificationManager.

// Нижний уровень (Деталь реализации)
class SmsService {
  public sendSms(phone: string, text: string) {
    console.log(`[SMS] Отправлено на ${phone}: ${text}`);
  }
}

// Верхний уровень (Бизнес-логика)
class NotificationManager {
  private smsService: SmsService;

  constructor() {
    // ЖЕСТКАЯ СВЯЗЬ: класс сам создает экземпляр конкретного SMS-сервиса
    this.smsService = new SmsService();
  }

  public notifyUser(message: string) {
    // Мы жестко привязаны к отправке именно SMS
    this.smsService.sendSms("+79991112233", message);
  }
}

// Использование
const manager = new NotificationManager();
manager.notifyUser("Привет!");

✅ Соблюдение принципа (хороший код)

Мы «выворачиваем» (инвертируем) зависимость. Создаем интерфейс MessageSender (абстракцию). Теперь наша бизнес-логика зависит только от этого интерфейса. Сам инструмент отправки передается (внедряется) снаружи через конструктор.

1. Создаем абстракцию (Интерфейс)

interface MessageSender {
  sendMessage(text: string): void;
}

2. Создаем разные детали реализации

class SmsSender implements MessageSender {
  public sendMessage(text: string): void {
    console.log(`[SMS] Отправлено: ${text}`);
  }
}

class EmailSender implements MessageSender {
  public sendMessage(text: string): void {
    console.log(`[Email] Отправлено: ${text}`);
  }
}

3. Класс бизнес-логики (Зависит только от абстракции)

Код NotificationManager стал чистым. Он понятия не имеет, как технически отправляется сообщение — по воздуху, по проводам или почтовым голубем. Он просто знает, что у объекта есть метод sendMessage.

class NotificationManager {
  // Зависимость от ИНТЕРФЕЙСА, а не от конкретного класса
  constructor(private sender: MessageSender) {}

  public notifyUser(message: string) {
    this.sender.sendMessage(message);
  }
}

// Использование (Клиентский код)
const sms = new SmsSender();
const email = new EmailSender();

// Мы можем легко менять способ отправки, вообще не трогая код NotificationManager:
const smsNotification = new NotificationManager(sms);
smsNotification.notifyUser("Ваш код: 4422");

const emailNotification = new NotificationManager(email);
emailNotification.notifyUser("Добро пожаловать на сайт!");

В чем выгода DIP?

  1. Слабая связанность (Loose Coupling): Модули изолированы друг от друга. Изменения в одном классе не ломают другие.
  2. Легкое тестирование: В тестах вместо реальной отправки SMS или запросов к базе данных вы можете передать фейковый объект-заглушку (Mock), который просто реализует нужный интерфейс.
  3. Готовность к изменениям: Замена одной технологии на другую (например, переход со старой СУБД на новую) происходит безболезненно.