Service Locator — это глобальный реестр сервисов. Любой класс в любой момент может запросить нужную зависимость через что-то вроде
ServiceLocator.Get<IEmailService>(). Выглядит удобно и гибко. На практике — это ловушка, которая усложняет поддержку кода с каждым месяцем.Марк Симанн назвал этот паттерн антипаттерном ещё в 2010 году. С тех пор аргументы стали только сильнее.
В чём проблема
Когда класс получает зависимости через конструктор, вы видите всё, что ему нужно, прямо в сигнатуре:
public class OrderService
{
private readonly IEmailService _email;
private readonly IOrderRepository _repo;
public OrderService(IEmailService email, IOrderRepository repo)
{
_email = email;
_repo = repo;
}
}
Два параметра — две зависимости. Всё на виду.
Теперь тот же класс через Service Locator:
public class OrderService
{
public void PlaceOrder(Order order)
{
var repo = ServiceLocator.Get<IOrderRepository>();
repo.Save(order);
var email = ServiceLocator.Get<IEmailService>();
email.SendConfirmation(order);
}
}
Снаружи
OrderService выглядит так, будто ему ничего не нужно. Зависимости спрятаны внутри метода. Чтобы понять, что класс использует, нужно читать весь его код. На десяти классах это неудобно. На сотне — это археология.Тесты превращаются в боль
Для юнит-теста с constructor injection вы просто передаёте моки в конструктор.
var service = new OrderService(mockEmail, mockRepo);
С Service Locator нужно сначала настроить глобальный реестр, зарегистрировать в нём все нужные моки, убедиться, что между тестами состояние очищается. Тесты становятся хрупкими и зависимыми друг от друга.
IServiceProvider в бизнес-логике — тот же Service Locator
В .NET есть встроенный DI-контейнер, и соблазн использовать
IServiceProvider напрямую велик. Но если вы инжектите IServiceProvider в бизнес-класс и достаёте из него сервисы вручную, это ровно тот же Service Locator, просто в обёртке от Microsoft:// Так делать не стоит
public class ReportGenerator
{
private readonly IServiceProvider _provider;
public ReportGenerator(IServiceProvider provider)
{
_provider = provider;
}
public void Generate()
{
var formatter = _provider.GetRequiredService<IReportFormatter>();
// ...
}
}
Зависимость от
IReportFormatter снова спрятана. Конструктор говорит только «мне нужен весь контейнер», что бесполезно.Правильный вариант:
public class ReportGenerator
{
private readonly IReportFormatter _formatter;
public ReportGenerator(IReportFormatter formatter)
{
_formatter = formatter;
}
public void Generate()
{
// _formatter уже здесь
}
}
Когда IServiceProvider допустим
Есть ограниченный список ситуаций, где обращение к
IServiceProvider оправдано. Фабрики, которые создают объекты с разным временем жизни. Плагинные системы, где набор сервисов неизвестен на этапе компиляции. Инфраструктурный код фреймворка, middleware, активаторы. Во всех этих случаях речь идёт об инфраструктуре, а не о бизнес-логике.Если ваш класс решает доменную задачу и при этом тянет
IServiceProvider, стоит вынести конкретную зависимость в конструктор. Код станет прозрачнее, тесты проще, а рефакторинг перестанет быть раскопками.📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#il_люминатор