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”.



0 comentário(s)