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 realizadoEste 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.
Desafio
FácilVamos 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 chamadaILogWriterno namespaceLogging. Esta interface representa o contrato para qualquer destino de log e deve ter um único métodoWrite(string message)que retorna uma string descrevendo onde a mensagem foi gravada.LogWriters.cs: Crie duas implementações concretas deILogWriterno namespaceLogging:ConsoleLogWriter- seu métodoWriteretorna"Console: " + messageFileLogWriter- recebe um nome de arquivo através de seu construtor e seu métodoWriteretorna"File[" + filename + "]: " + message
Logger.cs: Crie uma classeLoggerno namespaceLoggingque recebe sua dependência através de injeção por construtor. OLoggerdeve:- Aceitar um
ILogWriteratravé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
- Aceitar um
Program.cs: Demonstre a injeção de dependência criando dois loggers diferentes - um com umConsoleLogWritere outro com umFileLogWriter. Mostre como a mesma classeLoggerse 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 startedObserve 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 placedA 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
}
}
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
1Fundamentos de POO
Arquivos ExternosNamespaces e DiretivasIntrodução a Classes e ObjetosA Palavra-chave 'this'Métodos e ParâmetrosCampos vs PropriedadesConstrutoresInicializadores de ObjetosRecapitulação - Calculadora Simples4Herança
Sintaxe Básica de Herança (:)A Palavra-chave 'base'Palavras-chave Virtual e OverrideClasses SeladasA Classe Base 'object'Recapitulação - Hierarquia de Funcionários7Recursos Avançados
Sobrecarga de OperadoresIndexadores (this[])Sobrescrita de ToString()Métodos de ExtensãoRecapitulação - Lista Personalizada10Padrões de Projeto Parte 1
Introdução aos Padrões de ProjetoSingleton Thread-SafePadrão FactoryPadrão Observer (Eventos)Padrão Strategy2Propriedades e Membros Estáticos
Propriedades AutoimplementadasPropriedades de Apenas Leitura/EscritaCampos e Métodos EstáticosClasses EstáticasMembros com Corpo de Expressão5Polimorfismo e Interfaces
Polimorfismo: Compilação vs ExecuçãoInterface vs Classe AbstrataMúltiplas InterfacesInterfaces ExplícitasUpcasting e DowncastingRecapitulação - Calculadora de Formas8Conceitos Avançados de POO
Composição sobre HerançaGenerics (Classes e Métodos)Delegates e EventosAtributos e ReflectionIDisposable e a Instrução usingFundamentos de Injeção de Dependência11Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão Composite3Arquitetura de Classes
Dados de Instância vs EstáticosPalavras-chave 'readonly' e 'const'Campos de SuporteRecapitulação - Gerenciador de Conta Bancária6Encapsulamento
Modificadores de AcessoPropriedades para EncapsulamentoImplementação de Ocultação de DadosPadrões de ImutabilidadeRecapitulação - Registros de Estudantes9Argumentos Variáveis
A Palavra-chave 'params'Parâmetros OpcionaisArgumentos NomeadosSobrecarga de Métodos