Интенсивность ленивой зависимости

У меня есть проект, в котором Ninject используется как контейнер IoC. Моя забота заключается в том, что у многих классов есть такие конструкторы:

[Inject]
public HomeController(
    UserManager userManager, RoleManager roleManager, BlahblahManager blahblahManager) {
   _userManager = userManager;
   _roleManager = roleManager;
   _blahblahManager = blahblahManager;
}

Что делать, если я не хочу сразу иметь все экземпляры этих классов?

Способ, когда все эти классы обернуты Lazy<T> и переданы конструктору, не совсем то, что мне нужно. Экземпляры T еще не созданы, но Lazy<T> экземпляры уже сохранены в памяти.

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

Есть ли обходной путь для этой ситуации, или у IoC действительно есть такой большой недостаток в дизайне? Может быть, я должен использовать другой контейнер IoC?

Любые предложения?

Ответы

Ответ 1

Мне кажется, что вы делаете преждевременную оптимизацию: не делайте этого.

Конструкторы ваших сервисов не должны делать ничего более, чем хранить зависимости, которые он принимает в приватных полях. В таком случае создание такого объекта действительно легковесно. Не забывайте, что создание объектов в .NET действительно быстро. В большинстве случаев, с точки зрения производительности, просто не имеет значения, вводятся ли эти зависимости или нет. Особенно, если сравнивать с количеством объектов, которые выкладывает остальная часть вашего приложения (и используемые вами фреймворки). Реальные затраты - это когда вы начинаете использовать веб-сервисы, базы данных или файловую систему (или ввод-вывод в целом), потому что они вызывают гораздо большую задержку.

Если создание действительно дорого, вы обычно должны скрывать создание за Виртуальным прокси-сервером, а не внедрять Lazy<T> в каждого потребителя, так как это позволяет общему коду приложения не обращать внимания на тот факт, что существует механизм задержки создания (и ваш код приложения, и тестовый код становятся все более сложными, когда вы делаете это).

Глава 8 Внедрение зависимостей: принцип, практика, шаблоны содержит более подробное обсуждение ленивых и виртуальных прокси.

Однако Lazy<T> просто потребляет 20 байтов памяти (и еще 24 байта для его обернутого Func<T>, предполагая 32-битный процесс), и создание экземпляра Lazy<T> практически бесплатно. Так что об этом не нужно беспокоиться, кроме случаев, когда вы находитесь в среде с очень жесткими ограничениями памяти.

И если потребление памяти является проблемой, попробуйте зарегистрировать сервисы с временем жизни, превышающим переходный. Вы можете сделать за запрос, за веб-запрос или синглтон. Я бы даже сказал, что когда вы находитесь в среде, где создание новых объектов является проблемой, вам, вероятно, следует использовать только одноэлементные сервисы (но вряд ли вы работаете в такой среде, поскольку вы ' перестройка веб-приложения).

Обратите внимание, что Ninject - одна из медленных DI-библиотек для .NET. Если вас это беспокоит, переключитесь на более быстрый контейнер. Некоторые контейнеры имеют производительность, близкую к обновлению графов объектов вручную. но, во что бы то ни стало, при профиле этого многие разработчики переключают библиотеки DI по неправильным причинам.

Обратите внимание, что использование Lazy<T> в качестве зависимости является неплотной абстракцией (нарушение принципа инверсии зависимости). Пожалуйста, прочитайте этот ответ для получения дополнительной информации.

Ответ 2

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

Однако использование Lazy для выражения зависимости, в которой вы не нуждаетесь сразу, является обычным явлением в структурах внедрения зависимостей. Actofac - один из таких контейнеров, который имеет встроенную поддержку для различных типов упаковки. Я уверен, что есть также расширение для Ninject, возможно, взгляните на это, Ninject Lazy.

Ответ 3

Вы также можете добавить в метод действия следующий синтаксис. (Я не уверен, какая именно версия была представлена).

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

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

public IActionResult About([FromServices] IDateTime dateTime)
{
    ViewData["Message"] = "Currently on the server the time is " + dateTime.Now;

    return View();
}

https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/dependency-injection?view=aspnetcore-2.2#action-injection-with-fromservices