Для чего используется внедрение зависимостей (Dependency Injection)?

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

Ответ

Внедрение зависимостей (DI) — это паттерн проектирования, при котором зависимости объекта (сервисы, которые он использует) предоставляются ему извне, а не создаются внутри самого объекта. Это реализация принципа инверсии зависимостей (Dependency Inversion Principle, DIP).

Основные цели и преимущества:

  • Снижение связанности (Loose Coupling): Класс не зависит от конкретной реализации своей зависимости, а только от её абстракции (интерфейса).
  • Упрощение тестирования: В юнит-тесты легко подставлять заглушки (mocks) или стабы (stubs) через конструктор или сеттер.
  • Гибкость и поддерживаемость: Реализацию зависимости можно изменить в одном месте (конфигурации контейнера), не правя код всех использующих её классов.
  • Централизованное управление жизненным циклом: Контейнер DI (как Spring) управляет созданием и связыванием объектов.

Пример:

// 1. Высокоуровненый модуль зависит от абстракции (интерфейса)
interface NotificationService {
    void send(String message);
}

// 2. Конкретная реализация
class EmailService implements NotificationService {
    public void send(String message) { /* ... */ }
}

// 3. Класс, который использует сервис. Зависимость ВНЕДРЯЕТСЯ извне.
class OrderProcessor {
    private final NotificationService notifier;

    // Внедрение через конструктор (предпочтительный способ)
    public OrderProcessor(NotificationService notifier) {
        this.notifier = notifier; // Зависимость предоставлена
    }

    public void processOrder(Order order) {
        // ... логика обработки
        notifier.send("Order processed"); // Использование абстракции
    }
}

// 4. Конфигурация (например, в Spring)
@Configuration
public class AppConfig {
    @Bean
    public NotificationService notificationService() {
        return new EmailService(); // Здесь определяется конкретная реализация
    }

    @Bean
    public OrderProcessor orderProcessor(NotificationService service) {
        return new OrderProcessor(service); // Контейнер Spring сам внедрит зависимость
    }
}