Я думаю, что самый простой способ понять разницу между ними и почему DI-контейнер намного лучше, чем локатор служб, это подумать, почему мы вообще делаем инверсию зависимостей.
Мы делаем инверсию зависимостей, чтобы каждый класс явно указывал, от чего он зависит для работы. Мы делаем это, потому что это создает самую слабую связь, которую мы можем достичь. Чем слабее связь, тем проще что-то тестировать и реорганизовывать (и, как правило, требует наименьшего рефакторинга в будущем, потому что код чище).
Давайте посмотрим на следующий класс:
public class MySpecialStringWriter
{
private readonly IOutputProvider outputProvider;
public MySpecialFormatter(IOutputProvider outputProvider)
{
this.outputProvider = outputProvider;
}
public void OutputString(string source)
{
this.outputProvider.Output("This is the string that was passed: " + source);
}
}
В этом классе мы явно заявляем, что нам нужен IOutputProvider и ничего больше, чтобы этот класс работал. Это полностью тестируется и зависит от одного интерфейса. Я могу переместить этот класс в любое место моего приложения, включая другой проект, и все, что ему нужно, - это доступ к интерфейсу IOutputProvider. Если другие разработчики хотят добавить что-то новое в этот класс, что требует второй зависимости, они должны четко указать, что им нужно в конструкторе.
Взгляните на тот же класс с помощью сервисного локатора:
public class MySpecialStringWriter
{
private readonly ServiceLocator serviceLocator;
public MySpecialFormatter(ServiceLocator serviceLocator)
{
this.serviceLocator = serviceLocator;
}
public void OutputString(string source)
{
this.serviceLocator.OutputProvider.Output("This is the string that was passed: " + source);
}
}
Теперь я добавил сервисный локатор в качестве зависимости. Вот проблемы, которые сразу очевидны:
- Самая первая проблема заключается в том, что для достижения того же результата требуется больше кода . Больше кода это плохо. Это не намного больше кода, но это еще больше.
- Вторая проблема заключается в том, что моя зависимость больше не является явной . Мне все еще нужно ввести что-то в класс. Кроме того, что я хочу, не является явным. Оно скрыто в собственности того, что я просил. Теперь мне нужен доступ к ServiceLocator и IOutputProvider, если я хочу переместить класс в другую сборку.
- Третья проблема заключается в том, что другой разработчик может получить дополнительную зависимость , даже не подозревая, что он ее использует, когда добавляет код в класс.
- Наконец, этот код сложнее тестировать (даже если ServiceLocator является интерфейсом), потому что мы должны имитировать ServiceLocator и IOutputProvider вместо просто IOutputProvider
Так почему бы нам не сделать локатор службы статическим классом? Давайте взглянем:
public class MySpecialStringWriter
{
public void OutputString(string source)
{
ServiceLocator.OutputProvider.Output("This is the string that was passed: " + source);
}
}
Это намного проще, верно?
Неправильно.
Допустим, IOutputProvider реализован очень долго работающим веб-сервисом, который записывает строку в пятнадцать различных баз данных по всему миру и занимает очень много времени.
Давайте попробуем проверить этот класс. Нам нужна другая реализация IOutputProvider для теста. Как мы пишем тест?
Чтобы сделать это, нам нужно сделать некоторую необычную конфигурацию в статическом классе ServiceLocator, чтобы использовать другую реализацию IOutputProvider, когда он вызывается тестом. Даже писать это предложение было больно. Осуществление этого было бы мучительным, и это был бы кошмар обслуживания . Мы никогда не должны изменять класс специально для тестирования, особенно если этот класс не тот класс, который мы на самом деле пытаемся протестировать.
Итак, теперь у вас есть: а) тест, вызывающий навязчивые изменения кода в несвязанном классе ServiceLocator; или б) вообще никакого теста. И у вас остается менее гибкое решение.
Таким образом, класс локатора службы должен быть введен в конструктор. Это означает, что мы остались с конкретными проблемами, упомянутыми ранее. Локатор службы требует больше кода, сообщает другим разработчикам, что ему нужны вещи, которых нет, поощряет других разработчиков писать худший код и дает нам меньшую гибкость в продвижении вперед.
Проще говоря, сервисные локаторы увеличивают связность в приложении и побуждают других разработчиков писать высокосвязанный код .