Menu
Coddy logo textTech

Fundamentos de Injeção de Dependência

Parte da seção Programação Orientada a Objetos do Journey de C# da Coddy — lição 46 de 70.

Injeção de Dependência (DI) é uma técnica onde uma classe recebe suas dependências de fora, em vez de criá-las por si mesma. Isso torna o código mais flexível, testável e fracamente acoplado.

Sem DI, uma classe cria suas próprias dependências, tornando difícil a alteração ou o teste:

public class OrderService
{
    private EmailSender sender = new EmailSender();  // Fortemente acoplado
    
    public void PlaceOrder() => sender.Send("Order placed");
}

Com DI, as dependências são passadas através do construtor:

public interface IMessageSender
{
    void Send(string message);
}

public class EmailSender : IMessageSender
{
    public void Send(string message) => Console.WriteLine("Email: " + message);
}

public class OrderService
{
    private IMessageSender sender;
    
    public OrderService(IMessageSender sender)
    {
        this.sender = sender;
    }
    
    public void PlaceOrder() => sender.Send("Order placed");
}

Agora o OrderService não sabe nem se importa se está usando e-mail, SMS ou um remetente fictício (mock) para testes. A dependência é "injetada" quando o objeto é criado:

IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder();  // Email: Pedido realizado

Este padrão segue o princípio de depender de abstrações (interfaces) em vez de implementações concretas. Ele permite a troca de implementações sem modificar a classe que as utiliza, o que é essencial para testes de unidade e para manter arquiteturas flexíveis.

challenge icon

Desafio

Fácil

Vamos construir um sistema de log que demonstra o poder da injeção de dependência. Em vez de codificar rigidamente como os logs são gravados, você criará uma classe Logger flexível que pode funcionar com qualquer destino de log - console, simulação de arquivo ou qualquer outra coisa - sem conhecer os detalhes específicos.

Você organizará seu código em três arquivos:

  • ILogWriter.cs: Defina uma interface chamada ILogWriter no namespace Logging. Esta interface representa o contrato para qualquer destino de log e deve ter um único método Write(string message) que retorna uma string descrevendo onde a mensagem foi gravada.
  • LogWriters.cs: Crie duas implementações concretas de ILogWriter no namespace Logging:
    • ConsoleLogWriter - seu método Write retorna "Console: " + message
    • FileLogWriter - recebe um nome de arquivo através de seu construtor e seu método Write retorna "File[" + filename + "]: " + message
  • Logger.cs: Crie uma classe Logger no namespace Logging que recebe sua dependência através de injeção por construtor. O Logger deve:
    • Aceitar um ILogWriter através de seu construtor e armazená-lo em um campo privado
    • Ter um método Log(string message) que delega para o escritor injetado e retorna o resultado
  • Program.cs: Demonstre a injeção de dependência criando dois loggers diferentes - um com um ConsoleLogWriter e outro com um FileLogWriter. Mostre como a mesma classe Logger se comporta de forma diferente com base em qual dependência é injetada.

Você receberá duas entradas:

  • O nome do arquivo para o logger de arquivo
  • A mensagem para registrar

Crie um Logger com um ConsoleLogWriter e registre a mensagem. Em seguida, crie outro Logger com um FileLogWriter (usando o nome do arquivo da entrada) e registre a mesma mensagem. Imprima cada resultado em sua própria linha.

Por exemplo, se as entradas forem app.log e System started, a saída deve ser:

Console: System started
File[app.log]: System started

Observe como a classe Logger nunca muda - ela funciona com qualquer implementação de ILogWriter. Esta é a essência da injeção de dependência: o Logger depende de uma abstração (a interface), e a implementação concreta é "injetada" de fora. Você poderia facilmente adicionar um DatabaseLogWriter ou CloudLogWriter mais tarde sem modificar a classe Logger!

Folha de consulta

Injeção de Dependência (DI) é uma técnica onde uma classe recebe suas dependências de fora em vez de criá-las por si mesma, tornando o código mais flexível, testável e fracamente acoplado.

Sem DI (fortemente acoplado):

public class OrderService
{
    private EmailSender sender = new EmailSender();  // Fortemente acoplado
    
    public void PlaceOrder() => sender.Send("Order placed");
}

Com DI (fracamente acoplado):

public interface IMessageSender
{
    void Send(string message);
}

public class EmailSender : IMessageSender
{
    public void Send(string message) => Console.WriteLine("Email: " + message);
}

public class OrderService
{
    private IMessageSender sender;
    
    public OrderService(IMessageSender sender)
    {
        this.sender = sender;
    }
    
    public void PlaceOrder() => sender.Send("Order placed");
}

Injetando a dependência:

IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder();  // Email: Order placed

A DI segue o princípio de depender de abstrações (interfaces) em vez de implementações concretas, permitindo a troca de implementações sem modificar a classe que as utiliza.

Experimente você mesmo

using System;
using Logging;

class Program
{
    public static void Main(string[] args)
    {
        // Lê as entradas
        string filename = Console.ReadLine();
        string message = Console.ReadLine();

        // TODO: Criar um Logger com um ConsoleLogWriter e registrar a mensagem
        // Imprimir o resultado

        // TODO: Criar outro Logger com um FileLogWriter (usando o filename) e registrar a mesma mensagem
        // Imprimir o resultado
    }
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos