Muitos desenvolvedores Delphi, ao ouvirem falar de Injeção de Dependência (DI), pensam imediatamente em containers complexos ou frameworks como o Spring4D. Embora essas ferramentas sejam poderosas, elas são apenas facilitadores. A Injeção de Dependência, em sua essência, é um padrão de projeto — uma disciplina de codificação que não exige bibliotecas externas para ser aplicada.
Este artigo explora a “DI Pura” (Pure DI), focando em como desacoplar suas classes manualmente para transformar a manutenibilidade e a testabilidade do seu software legado ou novo.
O Problema: O “Veneno” da Instanciação Direta
Para entender a cura, precisamos diagnosticar a doença. No desenvolvimento Delphi tradicional (frequentemente herdado da cultura RAD – Rapid Application Development), é comum ver classes de negócio (Services, Controllers, DataModules) responsáveis por criar suas próprias dependências.
Imagine uma classe TPedidoService que precisa salvar um log de operação em um arquivo de texto. A implementação clássica (e acoplada) seria:
type
TPedidoService = class
public
procedure ProcessarPedido(PedidoID: Integer);
end;
implementation
uses
UFileLogger; // Acoplamento forte com a unidade concreta
procedure TPedidoService.ProcessarPedido(PedidoID: Integer);
var
Logger: TFileLogger;
begin
// ... Lógica de processamento do pedido ...
// O "Veneno": Instanciação direta dentro do método de negócio
Logger := TFileLogger.Create('C:\logs\app.log');
try
Logger.Log('Pedido ' + IntToStr(PedidoID) + ' processado.');
finally
Logger.Free;
end;
end;
Por que TClasse.Create aqui é ruim?
- Violação do SRP (Single Responsibility Principle): A classe
TPedidoServiceagora tem duas responsabilidades: processar o pedido E gerenciar o ciclo de vida do Logger. - Acoplamento Rígido: Se amanhã você precisar gravar o log no Banco de Dados ou na Nuvem, terá que alterar o código do
TPedidoService. Você violou o princípio Open/Closed (Aberto para extensão, fechado para modificação). - Morte dos Testes Unitários: Este é o ponto crítico. Se você tentar escrever um teste unitário para
ProcessarPedido, ele obrigatoriamente tentará criar um arquivo no disco (C:\logs\app.log). Isso torna o teste lento, dependente do sistema de arquivos e frágil. Você não consegue testar a lógica do pedido isoladamente.
A Solução: Injeção de Dependência via Construtor
A Injeção de Dependência é a aplicação prática do Princípio da Inversão de Dependência (DIP) do SOLID.
Regra de Ouro: Módulos de alto nível (Regra de Negócio) não devem depender de módulos de baixo nível (Arquivo, Banco, Socket). Ambos devem depender de abstrações (Interfaces).
Vamos refatorar o exemplo acima usando Constructor Injection manual.
Passo 1: Definir a Abstração (Interface)
Primeiro, definimos o contrato do que é um Logger, sem nos importarmos com como ele grava.
type
ILogger = interface
['{GUID-GERADO-AQUI}']
procedure Log(const Mensagem: String);
end;
Passo 2: Implementar a Abstração
Agora criamos a classe concreta que implementa a interface.
type
TFileLogger = class(TInterfacedObject, ILogger)
private
FFileName: String;
public
constructor Create(const FileName: String);
procedure Log(const Mensagem: String);
end;
Passo 3: Injetar a Dependência
Reescrevemos o TPedidoService. Agora, ele pede um ILogger em seu construtor. Ele não sabe se é um arquivo, um banco ou um console; ele apenas sabe que tem um objeto que obedece ao contrato ILogger.
type
TPedidoService = class
private
FLogger: ILogger; // Depende da Abstração
public
// Injeção via Construtor: Exige a dependência para nascer
constructor Create(ALogger: ILogger);
procedure ProcessarPedido(PedidoID: Integer);
end;
implementation
constructor TPedidoService.Create(ALogger: ILogger);
begin
// Guard clause para garantir que não recebemos nil
if ALogger = nil then
raise Exception.Create('ILogger é obrigatório');
FLogger := ALogger;
end;
procedure TPedidoService.ProcessarPedido(PedidoID: Integer);
begin
// ... Lógica de processamento ...
// Uso agnóstico da dependência
FLogger.Log('Pedido ' + IntToStr(PedidoID) + ' processado.');
end;
O Resultado: “Composition Root”
Agora que suas classes não criam mais suas dependências, quem cria? Em uma aplicação sem frameworks, essa responsabilidade sobe para o ponto de entrada da aplicação (o dpr ou o formulário principal). Este local é chamado de Composition Root.
// Exemplo no DPR ou Form Create principal
var
MeuLogger: ILogger;
ServicoPedido: TPedidoService;
begin
// 1. Prepara as dependências (Baixo nível)
MeuLogger := TFileLogger.Create('sistema.log');
// 2. Injeta no serviço (Alto nível)
ServicoPedido := TPedidoService.Create(MeuLogger);
try
ServicoPedido.ProcessarPedido(100);
finally
ServicoPedido.Free;
end;
end;
Isso nos dá flexibilidade total. Quer mudar o Log para o Console? Altere apenas a linha de criação do MeuLogger na raiz, e o resto do sistema funciona intacto.
O Benefício Real: Testabilidade
Lembra do problema com os testes unitários? Com a Injeção de Dependência Pura, resolver isso é trivial. Podemos criar um Mock (uma classe falsa) que implementa ILogger mas não faz nada (ou apenas valida se foi chamado).
type
TMockLogger = class(TInterfacedObject, ILogger)
public
WasCalled: Boolean;
procedure Log(const Mensagem: String);
end;
procedure TMockLogger.Log(const Mensagem: String);
begin
WasCalled := True; // Apenas marca que passou por aqui
end;
// No Teste Unitário (DUnit/DUnitX):
procedure TestarPedido;
var
Mock: TMockLogger;
Svc: TPedidoService;
begin
Mock := TMockLogger.Create;
Svc := TPedidoService.Create(Mock); // Injeta o Falso!
try
Svc.ProcessarPedido(123);
CheckTrue(Mock.WasCalled, 'O log deveria ter sido chamado');
finally
Svc.Free;
// Mock gerenciado por contagem de referência (Interface)
end;
end;
Conclusão
A Injeção de Dependência “Pura” no Delphi não é sobre escrever mais código, é sobre escrever código melhor. Ao remover o TClass.Create de dentro dos seus métodos de negócio e passá-los via constructor, você ganha:
- Clareza: O construtor da classe grita quais são suas dependências necessárias.
- Manutenibilidade: Alterar implementações de baixo nível não quebra a regra de negócio.
- Testabilidade: Capacidade total de isolar a classe para testes.
Domine este conceito manualmente. Quando você se sentir confortável e seus construtores começarem a ficar muito grandes, aí sim você estará pronto para usar um container DI (como Spring4D) para automatizar esse trabalho de “ligar os fios”.
Descubra mais sobre Régys Borges da Silveira
Assine para receber nossas notícias mais recentes por e-mail.
Dê-nos sua opinião, seu comentário ajuda o site a crescer e melhorar a qualidade dos artigos.