В чем разница между Dependency Injection (внедрение зависимостей) и Service Locator (локатор служб)?

«В чем разница между Dependency Injection (внедрение зависимостей) и Service Locator (локатор служб)?» — вопрос из категории Паттерны, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Оба подхода решают проблему создания зависимостей, но делают это принципиально по-разному, что влияет на тестируемость и прозрачность кода.

Dependency Injection (DI, Внедрение зависимостей): Зависимости явно передаются классу извне (через конструктор, метод или свойство). Класс ничего не знает о том, как создаются его зависимости.

// Зависимости передаются явно через конструктор.
public class UserService {
    private final UserRepository userRepository;
    private final EmailService emailService;

    // Зависимости ВНЕДРЯЮТСЯ. Код ясно показывает, что нужно для работы сервиса.
    public UserService(UserRepository userRepository, EmailService emailService) {
        this.userRepository = userRepository;
        this.emailService = emailService;
    }

    public void registerUser(String email) {
        User user = new User(email);
        userRepository.save(user);
        emailService.sendWelcomeEmail(user);
    }
}
// Тестирование простое: передаем моки-заглушки.
UserRepository mockRepo = mock(UserRepository.class);
EmailService mockEmail = mock(EmailService.class);
UserService service = new UserService(mockRepo, mockEmail);

Service Locator (SL, Локатор служб): Класс сам запрашивает свои зависимости из некоего глобального контейнера или реестра.

public class UserService {
    // Зависимости скрыты. Непонятно, что нужно для работы класса, не заглядывая в метод.
    public void registerUser(String email) {
        // Класс активно обращается к глобальному локатору.
        UserRepository userRepository = ServiceLocator.getService(UserRepository.class);
        EmailService emailService = ServiceLocator.getService(EmailService.class);

        User user = new User(email);
        userRepository.save(user);
        emailService.sendWelcomeEmail(user);
    }
}
// Тестирование сложнее: нужно настроить глобальный ServiceLocator, чтобы он возвращал моки.

Ключевая проблема Service Locator:

  1. Скрытые зависимости. Невозможно понять зависимости класса, глядя только на его публичный интерфейс (сигнатуру конструктора/методов). Нужно анализировать весь код.
  2. Усложнение тестирования. Тест зависит от глобального состояния (локатора), который нужно правильно настроить и очистить.
  3. Нарушение принципа инверсии зависимостей (DIP). Класс зависит от конкретной реализации локатора.

Вывод: DI является предпочтительным паттерном, так как он делает зависимости явными, код — более понятным, тестируемым и соответствующим принципам SOLID. Service Locator считается антипаттерном в контексте управления зависимостями, хотя может быть полезен в некоторых специфических сценариях (например, для разрешения зависимостей внутри фабрик).