Ответ
Да, активно применяю Dependency Injection (DI) и IoC-контейнеры для создания слабосвязанного, тестируемого и поддерживаемого кода. Основной опыт — со встроенным контейнером ASP.NET Core, а также с Autofac.
Основные концепции и практики:
-
Типы жизненного цикла:
Transient: Создается новый экземпляр на каждый запрос к контейнеру. Подходит для легковесных, stateless сервисов.Scoped: Один экземпляр создается на область видимости (например, на один HTTP-запрос в веб-приложении). Стандартный выбор для контекстных сервисов, таких какDbContextв EF Core.Singleton: Один экземпляр на все время жизни приложения. Используется для общих ресурсов (кэш, конфигурация).
-
Регистрация сервисов в ASP.NET Core:
public void ConfigureServices(IServiceCollection services) { // Регистрация по интерфейсу services.AddScoped<IUserRepository, UserRepository>(); services.AddSingleton<ILogger, FileLogger>(); services.AddTransient<IEmailService, EmailService>(); // Регистрация с фабрикой services.AddScoped<IService>(sp => new ConcreteService(sp.GetRequiredService<IDependency>()) ); }
Преимущества использования DI:
- Тестируемость: Зависимости легко подменить моками в unit-тестах.
- Гибкость: Изменение реализации сервиса требует правок только в точке регистрации.
- Управление жизненным циклом: Контейнер автоматически управляет созданием и освобождением ресурсов.
- Улучшение архитектуры: Код следует принципу Dependency Inversion (D из SOLID).
Пример внедрения в конструктор:
public class OrderProcessor
{
private readonly IPaymentGateway _paymentGateway;
private readonly IOrderRepository _repository;
// Зависимости автоматически разрешаются контейнером
public OrderProcessor(IPaymentGateway paymentGateway, IOrderRepository repository)
{
_paymentGateway = paymentGateway;
_repository = repository;
}
// ... методы, использующие зависимости
} Ответ 18+ 🔞
А, Dependency Injection! Ну это ж моя любимая тема, как же без неё, блядь. Без этого вообще в современном коде делать нехуй, если хочешь, чтобы твоё творение не развалилось при первом чихе и чтобы тестировать можно было без танцев с бубном.
Смотри, вся фишка в том, что ты не пишешь в коде new UserRepository() на каждом углу, а говоришь: "слушай, контейнер, мне нужен репозиторий пользователей, а какой именно — мне похуй, ты сам разберись". И он тебе подсовывает нужную реализацию. Красота же!
Основные приколы с жизненным циклом:
- Transient — это как одноразовые стаканчики. Каждый раз, когда просишь — новый, чистенький. Выпил — выбросил. Для всякой легкой, не помнящей состояния хуйни — самое то.
- Scoped — вот это уже серьёзнее. Один экземпляр на "сцену". В веб-приложении — на один HTTP-запрос. Все части запроса работают с одним и тем же экземпляром, например, с одним
DbContext. Закончился запрос — экземпляр на свалку истории. Удобно, логично. - Singleton — царь и бог, один на всё приложение. Создался при старте и будет жить, пока приложение не прикажет долго жить. Кэши, конфиги, логгеры — их по идее много и не надо.
Как это впихнуть в ASP.NET Core:
public void ConfigureServices(IServiceCollection services)
{
// Стандартная регистрация: "Когда просят IUserRepository — давай UserRepository"
services.AddScoped<IUserRepository, UserRepository>();
// Логгер один на всех, пусть живёт
services.AddSingleton<ILogger, FileLogger>();
// Отправка письма — лёгкая операция, каждый раз новый сервис
services.AddTransient<IEmailService, EmailService>();
// А тут уже с прибамбасами, если нужно свою логику создания
services.AddScoped<IService>(sp =>
new ConcreteService(sp.GetRequiredService<IDependency>()) // Достань мне из твоих закромов ещё одну зависимость
);
}
А почему это вообще охуенно?
- Тесты пишутся как по маслу. Хочешь протестировать
OrderProcessor? Подсовываешь ему в конструктор не настоящий платёжный шлюз, который будет списывать бабки, а заглушку (mock), которая говорит "всё прошло успешно, ёпта". И спишь спокойно. - Гибкость пиздецкая. Захотел заменить
FileLoggerнаCloudLogger? Одна строчка в регистрации сервисов — и всё приложение использует новую реализацию. Ничего нигде не ломается. - Жизненный цикл сам управляется. Не надо думать, когда создавать, а когда удалять объект. Контейнер умный, он всё сделает сам. Особенно это спасает с теми же
DbContext— чтобы они не жили дольше, чем надо. - Архитектура сразу становится менее дерьмовой. Соблюдается тот самый принцип инверсии зависимостей из SOLID. Высокоуровневые модули не зависят от низкоуровневых, а зависят от абстракций. Звучит занудно, но на деле — спасение.
Ну и как это выглядит в коде:
public class OrderProcessor
{
private readonly IPaymentGateway _paymentGateway; // Зависимость 1
private readonly IOrderRepository _repository; // Зависимость 2
// Конструктор — это наше всё. Контейнер видит: "Ага, ему нужны IPaymentGateway и IOrderRepository".
// Смотрит в свою базу зарегистрированных сервисов и ВУАЛЯ — передаёт сюда нужные реализации.
public OrderProcessor(IPaymentGateway paymentGateway, IOrderRepository repository)
{
_paymentGateway = paymentGateway;
_repository = repository;
}
// ... а дальше работаем, даже не зная, какие конкретно объекты нам подсунули
}
Вот и вся магия. Выглядит как overengineering для маленьких проектов, но как только проект вырастает больше, чем "Hello World", ты начинаешь понимать, что без этого — просто пипец, а не поддержка.