Что не так с глобальным созданием экземпляров сервисов при запуске приложения вместо внедрения зависимостей Ninject?

В настоящее время я использую Ninject для обработки DI в приложении С#/.Net/MVC. Когда я отслеживаю создание экземпляров своих сервисов, я обнаруживаю, что сервисы вызываются и создаются довольно часто в течение жизненного цикла, поэтому мне приходится создавать экземпляры сервисов и кэшировать их, а затем проверять кэшированные сервисы, прежде чем создавать экземпляры других. Конструкторы иногда довольно тяжелые).

Мне это кажется смешным, так как службам не нужны уникальные аргументы конструктора, поэтому их однократного создания достаточно для всей области приложения.

То, что я сделал в качестве быстрой альтернативы (просто для проверки концепции на данный момент, чтобы увидеть, работает ли она вообще), это...

  • Создал статический класс (называемый AppServices) со всеми моими сервисными интерфейсами в качестве его свойств.
  • Учитывая этот класс, метод Init(), который создает прямую реализацию каждого интерфейса службы из моей библиотеки служб. Это имитирует привязку их к ядру, если бы я использовал Ninject (или другой обработчик DI).

E.g.

public static class AppServices(){
  public IMyService MyService;
  public IMyOtherService MyOtherService;

  public Init(){
     MyService = new MyLib.MyService();
     MyOtherService =  new MyLib.MyOtherService();
  }
}
  • В App_Start я вызываю метод Init() для создания списка глобально доступных служб, экземпляры которых создаются только один раз.
  • С тех пор каждый раз, когда мне нужен экземпляр службы, я получаю его от AppServices. Таким образом, мне не нужно продолжать создавать новые экземпляры, которые мне не нужны.

E.g.

var IMyService _myService = AppServices.MyService;

Это отлично работает, и у меня пока не возникало НИКАКИХ проблем. Моя проблема в том, что это кажется слишком простым. Это всего лишь несколько строк кода, создающих статический класс в области приложения. Поскольку он делает именно то, что мне нужно для Ninject, но (как мне кажется, для моих целей) намного чище и экономит производительность, зачем мне нужен Ninject? Я имею в виду эти сложные обработчики внедрения зависимостей создаются не просто так? Должно быть что-то не так с моей «простой» интерпретацией DI, я просто не вижу этого.

Может ли кто-нибудь сказать мне, почему создание глобального статического контейнера для моих экземпляров службы - плохая идея, и, возможно, объяснить, почему именно Ninject (или любой другой обработчик DI) так необходим. Я понимаю концепции DI, поэтому, пожалуйста, не пытайтесь объяснить, что делает его таким замечательным. Я знаю. Я хочу точно знать, что он делает под капотом, что так отличается от моего метода App_Start.

Спасибо


person user2910842    schedule 23.10.2013    source источник
comment
#1: Может ли кто-нибудь сказать мне, почему создание глобального статического контейнера для экземпляров моих сервисов — плохая идея, #2: Я понимаю принципы внедрения зависимостей, поэтому, пожалуйста, не пытайтесь объяснить что делает его таким замечательным. ИМХО, если бы вы действительно понимали, вам не нужно было бы задавать этот вопрос. Как вы собираетесь писать тесты для компонентов вашего приложения, если все они лезут в AppServices и хватают всякую всячину в свое удовольствие?   -  person Jon    schedule 23.10.2013
comment
Я не совсем уверен, что понимаю: вы сами реализуете кеширование или просто описываете, что вы отследили, что делает ninject? Потому что я надеюсь, что вы знаете, что вы можете просто связать что-то .InSingletonScope() и, таким образом, убедиться, что ctor выполняется только один раз. Нет необходимости реализовывать это самостоятельно. Существуют также другие области для более сложных требований (например, .InNamedScope, .InRequestScope). Кроме того, в большинстве случаев проверка того, кэшируется ли он, оказывает незначительное влияние на производительность.   -  person BatteryBackupUnit    schedule 23.10.2013
comment
@Джон Конечно. Это общедоступные поля, поэтому тесты могут перезаписывать их.   -  person Kris Vandermotten    schedule 23.10.2013
comment
@KrisVandermotten: Это очевидно, поэтому неудивительно, что это не решает проблему, верно? Статические данные относятся к одному AppDomain, а это означает, что вы не можете запускать два теста в одном и том же AppDomain (с этого момента будем просто обрабатывать) параллельно, если каждому из них требуется отдельная служба. И они будут этого хотеть — некоторые захотят работать с реальной службой X, некоторые будут вместо этого создавать имитацию службы X, чтобы протестировать ее. Где это оставляет вас?   -  person Jon    schedule 23.10.2013
comment
@Jon Конечно, вы можете запускать разные тесты с разными службами, если запускаете их один за другим. Если вы хотите запускать их параллельно, и каждый тест выполняется (т. е. запрашивает службы) в одном потоке (т. е. потоке, в котором выполняется тест), вы можете сделать эти поля локальными для потока.   -  person Kris Vandermotten    schedule 23.10.2013
comment
@KrisVandermotten: Если вы запускаете тесты последовательно, это может занять вечность. Если вы создаете службы для каждого потока (т. е. для каждого теста), то вы только что выиграли битву, но проиграли войну: статический реестр был создан именно потому, что мы хотели кэшировать службы вместо того, чтобы постоянно создавать новые экземпляры. И в обоих случаях реализация начинает выходить на тестовую среду, накладывая ограничения на ваш тест-раннер. Если вы все еще не убеждены, я сдаюсь.   -  person Jon    schedule 23.10.2013
comment
@Jon В потоке не то же самое, что в тесте. Как вы думаете, как работает ваш DI-контейнер? Он ограничен теми же ограничениями .NET! И вот еще вопрос: конечно, я должен иметь возможность протестировать свое приложение. Это необходимо. Но у меня есть и другие требования: рентабельная разработка или, например, эффективное выполнение во время выполнения, а также высокая читаемость и ремонтопригодность. Что, если мои требования к производительности окажутся настолько высокими, что затраты на DI будут значительными? DI был средством достижения цели, а не целью. Как и все остальное, когда это стоит на пути к вашим целям, не используйте его.   -  person Kris Vandermotten    schedule 23.10.2013
comment
@KrisVandermotten: У меня нет желания спорить дальше, хотя я категорически не согласен с некоторыми из ваших слов.   -  person Jon    schedule 23.10.2013
comment
@Jon Это совершенно нормально. Итак, я заключаю: ОП просит аргументов, и все, что вы можете сказать, это то, что если бы вы действительно поняли, вам не пришлось бы спрашивать, и у меня нет желания спорить. Похоже, вы почти религиозно верите, что DI — это хорошо, но не можете объяснить, почему это так. Знаешь что? Может быть, просто может быть, иногда это не так. Вопрос ОП: Может ли кто-нибудь сказать мне [...], что делает Ninject (или любой другой обработчик DI) таким необходимым. Это очень хороший вопрос, и вы, видимо, не можете на него ответить.   -  person Kris Vandermotten    schedule 23.10.2013
comment
@KrisVandermotten: Поправка: мне, видимо, нечего спорить с вами. Это общедоступные поля, поэтому тесты могут перезаписывать их, что свидетельствует об отсутствии базового понимания того, почему глобальное состояние — это плохо. Это все, что я хочу сказать на данный момент, хорошего дня.   -  person Jon    schedule 23.10.2013
comment
Спасибо, парни. Я действительно не слишком много думал о детальном тестировании. Для любого базового теста, который я бы провел, я бы перезаписал общедоступное свойство AppServices (как вы упоминаете @KrisVandermotten). Я определенно понимаю, почему в более крупном приложении, выполняющем множество тестов параллельно, это становится проблемой. Кроме того, я не знал, что могу использовать InSingletonScope для создания их только один раз. Спасибо @jon. Это имеет больше смысла, когда я начинаю думать о модульном тестировании. Из моего ответа, вероятно, совершенно очевидно, что я не буду много делать. Я просто использую его для замены реализаций.   -  person user2910842    schedule 23.10.2013
comment
@KrisVandermotten - Спасибо за ваш вклад. Как вы сказали, я спрашиваю точки зрения, поскольку я явно не очень хорошо разбираюсь в себе. Все это хорошие моменты, и они начинают иллюстрировать, почему создаются эти фреймворки, поскольку моя статическая реализация имеет недостатки, которые, вероятно, укусят меня за задницу позже. Для этого конкретного проекта стоимость или масштаб не оправдывают большого бюджета DI, или я нанимаю кого-то, кто знает это намного лучше, чем я. Еще раз спасибо за информацию, ребята.   -  person user2910842    schedule 23.10.2013


Ответы (1)


Ваш вопрос нужно разделить на два вопроса:

  1. Действительно ли неправильно использовать шаблон singleton вместо внедрения зависимостей?
  2. Зачем мне нужен IoC-контейнер?

1)

Есть много причин, по которым вам не следует использовать шаблон singleton. Вот некоторые из основных:

Тестируемость

Да, вы можете протестировать статические экземпляры. Но вы не можете протестировать Изолированный (ПЕРВЫЙ). Я видел проекты, которые долго искали, почему тесты начинают сбоить без видимой причины, пока не поняли, что это из-за тестов, которые запускались в другом порядке. Когда у вас возникла эта проблема однажды, вы всегда будете хотеть, чтобы ваши тесты были как можно более изолированными. Статические значения пары тестов.

Это становится еще хуже, когда вы также выполняете интеграционное/специальное тестирование в дополнение к модульному тестированию.

Повторное использование

Вы не можете просто повторно использовать свои компоненты в других проектах. Другие проекты также должны будут использовать эту концепцию, даже если они решат использовать контейнер IoC.

Или вы не можете создать другой экземпляр вашего компонента с другими зависимостями. Зависимости компонентов будут жестко связаны с экземплярами в ваших AppServices. Вам придется изменить реализацию компонентов, чтобы использовать разные зависимости.

2) Выполнение DI не означает, что вы должны использовать какой-либо контейнер IoC. Вы можете реализовать свой собственный IDependencyResolver, который создает ваши контроллеры вручную и внедряет один и тот же экземпляр ваших служб везде, где они требуются. Контейнеры IoC используют некоторую производительность, но они упрощают создание ваших деревьев объектов. Вам придется решать самим, что важнее производительность или более простое создание ваших контроллеров.

person Remo Gloor    schedule 24.10.2013