Плохо ли использовать servicelocation вместо внедрения в конструктор, чтобы избежать загрузки фабричных классов?
Прямо сейчас мы используем DI/IOC, и когда нам нужно передать дополнительные параметры конструктору, мы используем фабричный класс, например
public class EmailSender
{
internal EmailSender(string toEmail, string subject,String body, ILogger emailLogger)
{.....}
}
public class EmailSenderFactory
{
ILogger emailLogger;
public EmailSenderFactory(ILogger emailLogger)
{
this.emailLogger = emailLogger;
}
public EmailSender Create(string toEmail, string subject, string body)
{
return new EmailSender(toEmail, subject, body, emailLogger);
}
}
Теперь проблема в том, что мы заканчиваем тем, что создаем целые лото-фабричные классы, и люди не всегда знают, как их использовать (они иногда сами их обновляют). Каковы самые большие недостатки кодирования такого класса:
public class EmailSender
{
EmailLogger logger = IoC.Resolve<ILogger>();
internal EmailSender(string toEmail, string subject,String body)
{.....}
}
Pro: теперь мы можем безопасно использовать конструктор, не нуждаясь в фабричном классе. Con: мы должны ссылаться на Service Locator (меня не беспокоит тестируемость, легко использовать фиктивный контейнер в качестве вспомогательного сервиса для контейнера).
Есть ли какая-то серьезная причина, почему мы не должны этого делать?
edit: после недолгих раздумий я решил, что, имея приватный конструктор и вложив класс Factory, я мог бы сохранить реализацию и фабрику вместе и предотвратить неправильное создание классов людьми, поэтому вопрос стал несколько спорным. Все пункты о том, что SL грязный, конечно, верны, поэтому приведенное ниже решение меня радует:
public class EmailSender
{
public class Factory
{
ILogger emailLogger;
public Factory(ILogger emailLogger)
{
this.emailLogger = emailLogger;
}
public EmailSender Create(string toEmail, string subject, string body)
{
return new EmailSender(toEmail, subject, body, emailLogger);
}
}
private EmailSender(string toEmail, string subject,String body, ILogger emailLogger)
{
}
}
3 ответа
Да - это плохо
- Зачем писать весь этот код, когда у вас может быть фреймворк? Все вызовы IoC.Resolve() излишни, и вам не нужно их писать.
Другой, даже более важный аспект, заключается в том, что ваши компоненты привязаны к локатору службы.
Теперь вы не можете создавать их экземпляры просто так - вам нужен полностью настроенный сервисный локатор каждый раз, когда вам нужно использовать компонент.
- И последнее, но самое меньшее - ваш SL-код разбросан по всей вашей кодовой базе, что не очень хорошая вещь, потому что когда вы хотите что-то изменить, вы должны искать в нескольких местах.
Самая большая причина, по которой я могу придумать (не обращая внимания на проблемы с локаторами служб в целом), заключается в том, что это не то, чего я ожидаю от пользователя вашего класса.
Мета обсуждение:
Некоторые DI-фреймворки (например, Guice) создадут фабрику для вас.
Некоторые люди выступают за отделение "нового" от "инъекционного".
Я не совсем уверен в этом сильном ответе "это плохо", который дал Кшиштоф. Я думаю, что здесь есть какой-то компромисс и предпочтение, не категоризируя их как плохие или хорошие.
- Я не думаю, что написание этих вызовов IoC.Resolve() более излишне, чем написание конкретных конструкторов или свойств для механизма внедрения.
- На этот раз я должен согласиться с тем, что вы привязаны к сервисному локатору и должны установить его перед созданием класса. Тем не мение:
- Вы можете разделить ваш локатор службы с более конкретными интерфейсами. Тем самым уменьшая связь с огромным сервисным локатором для всех сервисов вашей системы
- Да, если вы используете механизм DI, вы удалите все эти IoC.Resolve(), но вам все равно придется использовать своего рода контейнер для создания экземпляров ваших "основных" сервисов. ДИ должен перехватывать эти звонки, нет?
- Ваш сервисный локатор может (должен?) Быть "автоматически настраиваемым" или, по крайней мере, довольно простым в настройке.
- См. Выше пункт "разделите ваш локатор службы с более конкретными интерфейсами...".
Я думаю, что использование сервисного локатора действительно скрывает ваши зависимости внутри класса вместо того, чтобы показывать их через конструкторы. И это неудобно, на мой взгляд, потому что вы не узнаете, что ваш класс что-то упустил, пока не будет вызван локатор службы без его настройки.
Но вещь DI не свободна от такого рода мрака кода. Когда вы используете DI, не совсем понятно, как эти зависимости просто "появились" (магия DI) в вашем конструкторе. Используя SL, вы, по крайней мере, можете видеть, откуда берутся эти зависимости.
Но, тем не менее, при тестировании класса, который раскрывает эти зависимости от ее конструкторов, вы (почти) не можете пропустить это. Это не тот случай, когда используется сервисный локатор.
Я не говорю, что Кшиштоф был неправ, потому что я согласен с ним больше всего. Но я уверен, что использование сервисного локатора не обязательно является плохим "дизайном" и, конечно, не просто плохим.
Фил