Что такое dependency injection и зачем оно нужно?csharp-58

Определение Dependency Injection

Dependency Injection (DI) — это паттерн проектирования, реализующий принцип инверсии зависимостей (D из SOLID), при котором зависимости объекта не создаются внутри класса, а "внедряются" извне.

Классический пример без DI:

public class OrderService
{
    private readonly IOrderRepository _repository;

    public OrderService()
    {
        _repository = new SqlOrderRepository(); // Жесткая зависимость
    }

    public void ProcessOrder(Order order)
    {
        _repository.Save(order);
    }
}

Тот же класс с DI:

public class OrderService
{
    private readonly IOrderRepository _repository;

    public OrderService(IOrderRepository repository) // Зависимость внедряется
    {
        _repository = repository;
    }

    public void ProcessOrder(Order order)
    {
        _repository.Save(order);
    }
}

Основные виды внедрения зависимостей

  1. Через конструктор (наиболее предпочтительный):
public class ProductService
{
    private readonly IProductRepository _repo;

    public ProductService(IProductRepository repo)
    {
        _repo = repo;
    }
}
  1. Через свойства:
public class CustomerService
{
    public ICustomerRepository Repository { get; set; }
}
  1. Через методы:
public class ReportGenerator
{
    public void Generate(IReportFormatter formatter)
    {
        // ...
    }
}

Зачем нужно Dependency Injection?

1. Снижение связанности

  • Классы зависят от абстракций (интерфейсов), а не от конкретных реализаций
  • Легкая замена зависимостей без изменения основного кода

2. Упрощение тестирования

[Test]
public void ProcessOrder_ShouldSaveOrder()
{
    // Arrange
    var mockRepo = new Mock<IOrderRepository>();
    var service = new OrderService(mockRepo.Object);
    var order = new Order();

    // Act
    service.ProcessOrder(order);

    // Assert
    mockRepo.Verify(r => r.Save(order), Times.Once);
}

3. Централизованное управление зависимостями

  • Контейнер DI (например, в ASP.NET Core) управляет жизненным циклом объектов
  • Легко настраивать замену реализаций для всего приложения

4. Улучшение поддерживаемости кода

  • Явные зависимости (легче понимать, что требует класс)
  • Стандартизированный подход к созданию объектов

DI-контейнеры в .NET

Пример настройки в ASP.NET Core:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IOrderRepository, SqlOrderRepository>(); // Создается новый для каждого запроса
    services.AddScoped<IUserService, UserService>(); // Один экземпляр на запрос
    services.AddSingleton<ICacheService, RedisCacheService>(); // Один на все приложение
}

Жизненные циклы зависимостей

  1. Transient: Новый экземпляр для каждого запроса
  2. Scoped: Один экземпляр в пределах scope (например, web-запроса)
  3. Singleton: Один экземпляр на все приложение

Распространенные ошибки при использовании DI

  1. Сервис локатор (антипаттерн):
var repo = ServiceLocator.Get<IOrderRepository>(); // Плохо!
  1. Цепочка зависимостей (когда один сервис требует множество других)
  2. Циклические зависимости (A → B → C → A)
  3. Инжектирование слишком многих зависимостей (признак нарушения SRP)

Практические рекомендации

  1. Всегда предпочитайте внедрение через конструктор
  2. Используйте интерфейсы для абстракций
  3. Избегайте прямого создания зависимостей с помощью new
  4. Держите конструкторы простыми (максимум 3-4 зависимости)
  5. Для сложной инициализации используйте фабрики
// Пример фабрики для сложной инициализации
public interface IConnectionFactory
{
    IDbConnection CreateConnection();
}

public class SqlConnectionFactory : IConnectionFactory
{
    private readonly string _connectionString;

    public SqlConnectionFactory(IConfiguration config)
    {
        _connectionString = config.GetConnectionString("Default");
    }

    public IDbConnection CreateConnection() => new SqlConnection(_connectionString);
}

Резюмируем:

Dependency Injection — это мощный паттерн, который помогает создавать гибкие, тестируемые и поддерживаемые приложения. Правильное использование DI приводит к архитектуре, где компоненты слабо связаны и легко модифицируются, что особенно важно в современных сложных приложениях и микросервисных архитектурах.