Для чего нужен Dependency Injection (DI)?

«Для чего нужен Dependency Injection (DI)?» — вопрос из категории Архитектура, который задают на 28% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Dependency Injection (DI, Внедрение зависимостей) — это архитектурный паттерн, при котором зависимости объекта (сервисы, которые он использует) предоставляются ему извне, а не создаются внутри самого объекта. Это основа принципа Инверсии зависимостей (Dependency Inversion Principle, DIP) из SOLID.

Основные цели и преимущества:

  1. Снижение связанности (Loose Coupling): Классы зависят от абстракций (интерфейсов), а не от конкретных реализаций. Это делает систему гибче.
  2. Упрощение тестирования (Testability): Зависимости можно легко подменить mock- или stub-объектами в unit-тестах.
  3. Улучшение поддерживаемости: Изменение реализации сервиса затрагивает минимальное количество кода (обычно только конфигурацию DI-контейнера).
  4. Управление жизненным циклом: DI-контейнер (например, в ASP.NET Core) контролирует время жизни объектов (Singleton, Scoped, Transient).

Пример: Без DI vs. С DI

Проблема (Жёсткая связь):

public class OrderProcessor
{
    private readonly ILogger _logger = new FileLogger(); // Прямое создание зависимости

    public void Process(Order order)
    {
        _logger.Log("Processing started...");
        // Логика обработки
    }
}
// Тестировать сложно — всегда будет использоваться FileLogger.

Решение с DI (Внедрение через конструктор):

public interface ILogger { void Log(string message); }
public class FileLogger : ILogger { /*...*/ }
public class ConsoleLogger : ILogger { /*...*/ }

public class OrderProcessor
{
    private readonly ILogger _logger;

    // Зависимость "внедряется" извне
    public OrderProcessor(ILogger logger)
    {
        _logger = logger;
    }

    public void Process(Order order) { /*...*/ }
}

// Использование и тестирование:
ILogger logger = new ConsoleLogger(); // Или FileLogger(), или MockLogger
var processor = new OrderProcessor(logger); // Гибко!
processor.Process(order);

// В unit-тесте:
var mockLogger = new Mock<ILogger>();
var processorUnderTest = new OrderProcessor(mockLogger.Object);
// Легко проверить, был ли вызван метод Log

DI в ASP.NET Core встроен и настраивается в Startup.cs или Program.cs:

// Регистрация сервисов в контейнере
services.AddScoped<ILogger, FileLogger>(); // Один экземпляр на область видимости (запрос)
services.AddSingleton<ICacheService, RedisCache>(); // Один экземпляр на всё приложение
services.AddTransient<IEmailSender, SmtpEmailSender>(); // Новый экземпляр каждый раз

// Контейнер автоматически внедрит ILogger в OrderProcessor