Injeção pelo construtor e interfaces
Aplique a injeção pelo construtor com um design baseado em interfaces para desacoplar componentes e permitir testes unitários.
Injeção pelo construtor e interfaces é uma aula grátis de C# Academy no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de C# Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de C# Academy inclui 4 aulas no total.
Fundamentos da injeção pelo construtor
A injeção pelo construtor é o padrão de DI mais comum. Uma classe declara suas dependências como parâmetros do construtor, e o contêiner as fornece no momento da instanciação.
Isso torna as dependências explícitas, obrigatórias e visíveis.
Definindo a interface
Comece com uma interface que defina o contrato. Isso permite trocar diferentes implementações (real, simulada ou em memória) sem alterar o código dependente.
public interface IProductRepository
{
Task<Product?> GetByIdAsync(int id);
Task<IEnumerable<Product>> GetAllAsync();
Task AddAsync(Product product);
}Implementando a interface
A implementação concreta executa o trabalho real — chamadas ao banco de dados, solicitações HTTP etc. O consumidor nunca precisa saber qual implementação recebe.
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();
}
}Consumindo por meio do construtor
A camada de serviço recebe IProductRepository como parâmetro do construtor. Ela nunca conhece SqlProductRepository — apenas o contrato da interface.
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);
}
}Registrando a cadeia
Registre todos os serviços da cadeia. O contêiner resolve automaticamente todo o grafo de dependências — ProductService → IProductRepository → AppDbContext.
builder.Services.AddDbContext<AppDbContext>(opt =>
opt.UseSqlite("Data Source=app.db"));
builder.Services.AddScoped<IProductRepository, SqlProductRepository>();
builder.Services.AddScoped<ProductService>();Testando com um objeto simulado
Como as dependências são injetadas por meio de interfaces, os testes unitários podem receber um objeto simulado ou falso sem acessar o banco de dados. Esse é um dos maiores benefícios da 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);Vários parâmetros de construtor
Uma classe pode ter muitos parâmetros de construtor. O contêiner resolve todos, desde que cada um esteja registrado. Mantenha os construtores focados — parâmetros demais são um indício de que a classe tem responsabilidades demais.
public class OrderService
{
public OrderService(
IOrderRepository orders,
IProductRepository products,
IEmailSender email,
ILogger<OrderService> logger)
{
// all injected by DI container
}
}Evitando o antipadrão do localizador de serviços
Não (NOT) injete IServiceProvider e chame GetService dentro de métodos de negócio. Isso oculta as dependências e dificulta os testes. Prefira a injeção pelo construtor para obter um código explícito e testável.
// 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;
}Sintaxe de construtor primary (C# 12)
O C# 12 introduz construtores primary para todos os tipos de classe, permitindo declarar parâmetros diretamente na declaração da classe para obter um código de DI mais conciso.
// 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);
}
}Trocando implementações
Uma vantagem importante da DI baseada em interfaces é poder trocar uma implementação em uma única linha sem alterar nenhum código consumidor. Isso é útil para alternar entre bancos de dados, provedores de e-mail ou sinalizadores de funcionalidade.
// Switch from SQL to in-memory for integration tests
if (environment.IsEnvironment("Test"))
builder.Services.AddScoped<IProductRepository, InMemoryProductRepository>();
else
builder.Services.AddScoped<IProductRepository, SqlProductRepository>();Na prática: controlador com DI
Os controladores do ASP.NET Core usam nativamente a injeção pelo construtor. A estrutura resolve todos os parâmetros e os fornece ao controlador.
[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);
}
}Verificação rápida
Qual é o principal benefício de programar para interfaces ao usar DI?
Recapitulação: injeção pelo construtor e interfaces
Principais conclusões:
- Declare as dependências como parâmetros do construtor — o contêiner as fornece
- Defina interfaces para os contratos dos serviços; injete a interface, não o tipo concreto
- As interfaces permitem criar objetos simulados facilmente nos testes unitários e trocar implementações
- Os construtores primary do C# 12 reduzem o código repetitivo em classes com muitas dependências injetadas
- Evite o antipadrão do localizador de serviços — mantenha as dependências explícitas
Perguntas Frequentes
A aula “Injeção pelo construtor e interfaces” é grátis?
Sim — o texto completo de “Injeção pelo construtor e interfaces” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de C# Academy, atualize para CoddyKit PRO. O curso de C# Academy inclui 4 aulas no total.
O que vou aprender em “Injeção pelo construtor e interfaces”?
Aplique a injeção pelo construtor com um design baseado em interfaces para desacoplar componentes e permitir testes unitários. Você pratica C# Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar C# Academy?
Nenhuma experiência prévia é necessária. C# Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.
Quanto tempo leva a aula “Injeção pelo construtor e interfaces”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de C# Academy?
Sim. Cada aula de C# Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Fundamentos do contêiner de DI
- Ciclos de vida dos serviços: Transient, Scoped e Singleton
- Injeção pelo construtor e interfaces
- Padrões Factory e Options