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

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

Ответ

Принцип инверсии зависимостей (DIP) — это пятый принцип SOLID. Его суть:

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

Пример: Логирование в приложении

Нарушение DIP: Высокоуровневый OrderService напрямую зависит от низкоуровневого FileLogger.

// Низкоуровневый модуль (деталь)
class FileLogger {
    public void log(String message) {
        // Запись в файл
    }
}

// Высокоуровневый модуль (политика) зависит от детали
class OrderService {
    private FileLogger logger = new FileLogger(); // Жёсткая привязка
    public void processOrder(Order order) {
        // ... логика обработки
        logger.log("Order processed: " + order.getId());
    }
}
// Проблема: Смена логгера на ConsoleLogger требует изменения кода OrderService.

Соблюдение DIP: Оба уровня зависят от абстракции Logger.

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

// 2. Низкоуровневые модули (детали) реализуют абстракцию
class FileLogger implements Logger {
    @Override
    public void log(String message) { /* запись в файл */ }
}

class ConsoleLogger implements Logger {
    @Override
    public void log(String message) { /* вывод в консоль */ }
}

// 3. Высокоуровневый модуль зависит только от абстракции
class OrderService {
    private Logger logger; // Зависимость от абстракции
    // 4. Конкретная реализация внедряется извне (Инверсия Управления)
    public OrderService(Logger logger) {
        this.logger = logger;
    }
    public void processOrder(Order order) {
        // ... логика обработки
        logger.log("Order processed: " + order.getId());
    }
}

// 5. Конфигурация (где происходит "инверсия")
public class AppConfig {
    public static void main(String[] args) {
        // Выбор конкретной детали определяется здесь, а не в OrderService.
        Logger logger = new ConsoleLogger(); // Легко заменить на FileLogger
        OrderService service = new OrderService(logger);
        service.processOrder(new Order());
    }
}

Ключевые выводы:

  • Инверсия заключается в том, что традиционное направление зависимости (сервис → конкретный логгер) переворачивается. Теперь и сервис, и логгер зависят от общего интерфейса.
  • DIP тесно связан с внедрением зависимостей (DI) и инверсией управления (IoC), которые являются практическими шаблонами для его реализации.
  • Результат: код становится слабосвязанным, расширяемым и легко тестируемым (можно подменить реальный логгер моком).