Перейти к основному содержимому

5.05. Dependency Injection

Разработчику Архитектору

Dependency Injection

Dependency Injection (DI) — это паттерн проектирования, который позволяет внедрять зависимости (сервисы) в классы извне, а не создавать их внутри. Это делает код гибким, тестируемым и поддерживаемым. В .NET DI встроен в ядро фреймворка.

Зависимость (Dependency) — это любой внешний сервис, который использует ваш класс:

  • Базы данных (DbContext)
  • HTTP-клиенты (HttpClient)
  • Логгеры (ILogger)
  • Репозитории, внешние API и т. д.

Внедрение зависимостей — это передача этих сервисов извне (через конструктор, свойства или методы), а не создание их внутри класса. Без DI, зависимости создаются внутри класса, образуя «жесткую связь». Это сложно тестировать, менять и это нарушает один из принципов проектирования SOLID - принцип единственной ответственности (SRP).

public class OrderService
{
private readonly ILogger _logger = new FileLogger(); // Жёсткая привязка
private readonly IPaymentGateway _gateway = new PayPalGateway(); // Трудно тестировать

public void ProcessOrder(Order order)
{
_logger.Log("Processing order...");
_gateway.ProcessPayment(order.Total);
}
}

Хорошим кодом считается внедрение зависимостей извне:

public class OrderService
{
private readonly ILogger _logger;
private readonly IPaymentGateway _gateway;

// Зависимости передаются через конструктор
public OrderService(ILogger logger, IPaymentGateway gateway)
{
_logger = logger;
_gateway = gateway;
}

public void ProcessOrder(Order order)
{
_logger.Log("Processing order...");
_gateway.ProcessPayment(order.Total);
}
}

Как они внедряются?

  1. Через конструктор (Constructor Injection). Это самый популярный способ, зависимости передаются при создании объекта:
public class UserService
{
private readonly IUserRepository _repo;

public UserService(IUserRepository repo)
{
_repo = repo;
}
}
  1. Через свойства (Property Injection). Реже используется, но подходит для опциональных зависимостей:
public class ReportGenerator
{
public ILogger Logger { get; set; } // Можно не задавать
}
  1. Через методы (Method Injection). Зависимость передаётся в конкретный метод:
public class DataExporter
{
public void Export(ISerializer serializer) { ... }
}

.NET предоставляет встроенный IoC-контейнер (IServiceProvider), который управляет зависимостями.

Так работает регистрация сервисов в Program.cs или Startup.cs.

var builder = WebApplication.CreateBuilder(args);

// 1. Singleton (один экземпляр на всё приложение)
builder.Services.AddSingleton<ILogger, FileLogger>();

// 2. Scoped (один экземпляр на запрос в веб-приложении)
builder.Services.AddScoped<IUserRepository, UserRepository>();

// 3. Transient (новый экземпляр при каждом запросе)
builder.Services.AddTransient<IEmailService, EmailService>();

var app = builder.Build();
  • Singleton - один экземпляр на всё приложение. Подходит для кеша, конфигурации.
  • Scoped - один экземпляр на область (например, HTTP-запрос).
  • Transient - новый экземпляр при каждом обращении. Подходит для лёгких сервисов.