Ответ
Service Locator и Inversion of Control (IoC) через Dependency Injection (DI) — это два подхода к управлению зависимостями, но с противоположными последствиями для качества кода.
Dependency Injection (Внедрение зависимостей)
- Принцип: Зависимости класса явно объявляются (обычно через параметры конструктора) и предоставляются извне контейнером IoC.
- Преимущества: Прозрачность зависимостей, простота модульного тестирования (можно передать mock/stub), соблюдение принципа инверсии зависимостей (DIP).
// Зависимости ЯВНЫЕ и обязательные
public class OrderProcessor
{
private readonly IPaymentGateway _paymentGateway;
private readonly IEmailService _emailService;
// Зависимости инжектируются. Класс не знает, как их создать.
public OrderProcessor(IPaymentGateway paymentGateway, IEmailService emailService)
{
_paymentGateway = paymentGateway ?? throw new ArgumentNullException(nameof(paymentGateway));
_emailService = emailService ?? throw new ArgumentNullException(nameof(emailService));
}
public async Task ProcessOrder(Order order)
{
await _paymentGateway.ChargeAsync(order.Total);
await _emailService.SendReceiptAsync(order);
}
}
// Тестирование тривиально
var mockGateway = new Mock<IPaymentGateway>();
var mockEmail = new Mock<IEmailService>();
var processor = new OrderProcessor(mockGateway.Object, mockEmail.Object);
// ... assertions
Service Locator (Локатор служб) — антипаттерн
- Принцип: Класс скрыто запрашивает свои зависимости из глобального или статического контейнера.
- Недостатки: Скрытые зависимости, усложнённое тестирование (требуется настройка локатора перед каждым тестом), нарушение DIP, код становится жёстко связанным с конкретной реализацией локатора.
// Зависимости СКРЫТЫ и необязательные (на этапе компиляции)
public class OrderProcessor
{
public OrderProcessor() // Конструктор пуст. Какие зависимости? Неясно.
{ }
public async Task ProcessOrder(Order order)
{
// Магическое извлечение зависимостей. Класс зависит от глобального состояния.
var paymentGateway = ServiceLocator.Current.GetService<IPaymentGateway>();
var emailService = ServiceLocator.Current.GetService<IEmailService>();
// Что, если локатор не сконфигурирован или сервис не зарегистрирован?
// Runtime-ошибка, а не compile-time ошибка.
await paymentGateway.ChargeAsync(order.Total);
await emailService.SendReceiptAsync(order);
}
}
// Тестирование становится кошмаром
[TestInitialize]
public void Setup()
{
// Необходимо настроить глобальный статический локатор для каждого теста
var mockGateway = new Mock<IPaymentGateway>();
ServiceLocator.SetLocatorProvider(() => new MockServiceLocator(mockGateway.Object));
}
// Это хрупко и не масштабируется.
Итог
- IoC/DI — это современный best practice, ведущий к чистой, тестируемой и поддерживаемой архитектуре.
- Service Locator считается антипаттерном, так как маскирует истинные зависимости класса, делая систему менее предсказуемой и более сложной в сопровождении. Избегайте его в новом коде.