В чём заключается принцип инверсии зависимостей (DIP)? Приведите пример.

«В чём заключается принцип инверсии зависимостей (DIP)? Приведите пример.» — вопрос из категории ООП, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Принцип инверсии зависимостей (DIP) — это один из принципов SOLID. Он гласит:

  1. Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций.
  2. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Цель — уменьшить связность между компонентами, сделав систему более гибкой, расширяемой и удобной для тестирования.

Пример: Система оповещений

Без DIP (жёсткая зависимость):

// Низкоуровневый модуль
class EmailService {
    public void sendEmail(String message) { /* ... */ }
}

// Высокоуровневый модуль напрямую зависит от низкоуровневого
class NotificationService {
    private EmailService emailService = new EmailService(); // Прямая зависимость
    public void notify(String message) {
        emailService.sendEmail(message);
    }
}
// Добавление SMS требует изменения NotificationService.

С применением DIP (зависимость от абстракции):

// 1. Абстракция (интерфейс), от которой зависят оба уровня
interface MessageSender {
    void send(String message);
}

// 2. Низкоуровневые модули реализуют абстракцию
class EmailService implements MessageSender {
    @Override
    public void send(String message) { /* ... */ }
}

class SmsService implements MessageSender {
    @Override
    public void send(String message) { /* ... */ }
}

// 3. Высокоуровневый модуль зависит только от абстракции
class NotificationService {
    private MessageSender sender; // Зависимость от абстракции
    // 4. Внедрение зависимости (через конструктор)
    public NotificationService(MessageSender sender) {
        this.sender = sender;
    }
    public void notify(String message) {
        sender.send(message); // Работает с любой реализацией
    }
}

// Использование. Конкретная реализация "инвертируется" и внедряется извне.
public class App {
    public static void main(String[] args) {
        MessageSender sender = new SmsService(); // Легко заменить на EmailService
        NotificationService service = new NotificationService(sender);
        service.notify("Hello!");
    }
}

Итог: NotificationService (верхний уровень) и EmailService/SmsService (нижний уровень) зависят от общего интерфейса MessageSender. Управление зависимостями передаётся внешнему коду (например, контейнеру IoC в Spring), что соответствует инверсии управления (IoC).