0Pricing
C# Academy · Урок

Внедрение через конструктор и интерфейсы

Применяйте внедрение через конструктор и проектирование на основе интерфейсов, чтобы ослабить связанность компонентов и обеспечить модульное тестирование.

«Внедрение через конструктор и интерфейсы» — бесплатный урок C# Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.

Основы внедрения через конструктор

Внедрение через конструктор — наиболее распространённый шаблон DI. Класс объявляет свои зависимости как параметры конструктора, а контейнер предоставляет их во время создания экземпляра.

Благодаря этому зависимости становятся явными, обязательными и видимыми.

Определение интерфейса

Начните с интерфейса, задающего контракт. Это позволяет заменять разные реализации (настоящую, имитацию или реализацию в памяти), не изменяя зависимый код.

public interface IProductRepository
{
    Task<Product?> GetByIdAsync(int id);
    Task<IEnumerable<Product>> GetAllAsync();
    Task AddAsync(Product product);
}

Реализация интерфейса

Конкретная реализация выполняет настоящую работу — обращается к базе данных, отправляет HTTP-запросы и так далее. Потребителю не нужно знать, какую именно реализацию он получает.

public class SqlProductRepository : IProductRepository
{
    private readonly AppDbContext _db;

    public SqlProductRepository(AppDbContext db) => _db = db;

    public async Task<Product?> GetByIdAsync(int id)
        => await _db.Products.FindAsync(id);

    public async Task<IEnumerable<Product>> GetAllAsync()
        => await _db.Products.ToListAsync();

    public async Task AddAsync(Product product)
    {
        _db.Products.Add(product);
        await _db.SaveChangesAsync();
    }
}

Использование через конструктор

Слой служб принимает IProductRepository как параметр конструктора. Он ничего не знает о SqlProductRepository — только о контракте интерфейса.

public class ProductService
{
    private readonly IProductRepository _repo;
    private readonly ILogger<ProductService> _logger;

    public ProductService(
        IProductRepository repo,
        ILogger<ProductService> logger)
    {
        _repo = repo;
        _logger = logger;
    }

    public async Task<Product?> GetProductAsync(int id)
    {
        _logger.LogInformation("Fetching product {Id}", id);
        return await _repo.GetByIdAsync(id);
    }
}

Регистрация цепочки

Зарегистрируйте каждую службу в цепочке. Контейнер автоматически разрешит весь граф зависимостей — ProductService → IProductRepository → AppDbContext.

builder.Services.AddDbContext<AppDbContext>(opt =>
    opt.UseSqlite("Data Source=app.db"));

builder.Services.AddScoped<IProductRepository, SqlProductRepository>();
builder.Services.AddScoped<ProductService>();

Проверка с имитацией

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

// Using NSubstitute or Moq in a unit test
var mockRepo = Substitute.For<IProductRepository>();
mockRepo.GetByIdAsync(1).Returns(new Product { Id = 1, Name = "Widget" });

var logger = Substitute.For<ILogger<ProductService>>();
var svc = new ProductService(mockRepo, logger);

var result = await svc.GetProductAsync(1);
Assert.Equal("Widget", result?.Name);

Несколько параметров конструктора

У класса может быть много параметров конструктора. Контейнер разрешит их все, если каждый параметр зарегистрирован. Делайте конструкторы целенаправленными: слишком большое количество параметров указывает на то, что у класса слишком много обязанностей.

public class OrderService
{
    public OrderService(
        IOrderRepository orders,
        IProductRepository products,
        IEmailSender email,
        ILogger<OrderService> logger)
    {
        // all injected by DI container
    }
}

Отказ от антишаблона локатора служб

Не (NOT) внедряйте IServiceProvider и не вызывайте GetService внутри бизнес-методов. Это скрывает зависимости и усложняет проверки. Предпочитайте внедрение через конструктор, чтобы код был понятным и пригодным для проверки.

// BAD
public void Process(IServiceProvider sp)
{
    var repo = sp.GetService<IProductRepository>(); // hidden dep!
}

// GOOD
public class Processor
{
    private readonly IProductRepository _repo;
    public Processor(IProductRepository repo) => _repo = repo;
}

Синтаксис основного конструктора (C# 12)

В C# 12 появились основные конструкторы для всех типов классов. Они позволяют объявлять параметры непосредственно в объявлении класса и писать более лаконичный код DI.

// C# 12 primary constructor
public class ProductService(
    IProductRepository repo,
    ILogger<ProductService> logger)
{
    public async Task<Product?> GetAsync(int id)
    {
        logger.LogInformation("Getting {Id}", id);
        return await repo.GetByIdAsync(id);
    }
}

Замена реализаций

Ключевое преимущество DI на основе интерфейсов: реализацию можно заменить одной строкой, не изменяя код, который её использует. Это удобно при смене баз данных, служб рассылки или флагов возможностей.

// Switch from SQL to in-memory for integration tests
if (environment.IsEnvironment("Test"))
    builder.Services.AddScoped<IProductRepository, InMemoryProductRepository>();
else
    builder.Services.AddScoped<IProductRepository, SqlProductRepository>();

Практический пример: контроллер с DI

Контроллеры ASP.NET Core изначально поддерживают внедрение через конструктор. Платформа разрешает все параметры и передаёт их контроллеру.

[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
    private readonly ProductService _svc;

    public ProductsController(ProductService svc) => _svc = svc;

    [HttpGet("{id}")]
    public async Task<IActionResult> Get(int id)
    {
        var product = await _svc.GetProductAsync(id);
        return product is null ? NotFound() : Ok(product);
    }
}

Быстрая проверка

Каково главное преимущество программирования через интерфейсы при использовании DI?

Итоги: внедрение через конструктор и интерфейсы

Главное:

  • Объявляйте зависимости как параметры конструктора — контейнер предоставит их
  • Определяйте интерфейсы для контрактов служб; внедряйте интерфейс, а не конкретный тип
  • Интерфейсы упрощают создание имитаций в модульных проверках и замену реализаций
  • Основные конструкторы C# 12 сокращают шаблонный код в классах с большим количеством зависимостей DI
  • Избегайте антишаблона локатора служб — зависимости должны оставаться явными

Часто задаваемые вопросы

Урок «Внедрение через конструктор и интерфейсы» бесплатный?

Да — полный текст урока «Внедрение через конструктор и интерфейсы» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.

Чему я научусь в уроке «Внедрение через конструктор и интерфейсы»?

Применяйте внедрение через конструктор и проектирование на основе интерфейсов, чтобы ослабить связанность компонентов и обеспечить модульное тестирование. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать C# Academy?

Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Внедрение через конструктор и интерфейсы»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке C# Academy?

Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Основы контейнера внедрения зависимостей
  2. Время жизни служб: Transient, Scoped, Singleton
  3. Внедрение через конструктор и интерфейсы
  4. Шаблоны фабрики и параметров
← Назад к C# Academy