Tecnologia além do código.
Régys Borges da Silveira

Desacoplando seu Projeto Delphi: Injeção de Dependência sem Frameworks

injeção de dependência
Neste artigo 7 tópicos

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?

  1. Violação do SRP (Single Responsibility Principle): A classe TPedidoService agora tem duas responsabilidades: processar o pedido E gerenciar o ciclo de vida do Logger.
  2. 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).
  3. 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:

  1. Clareza: O construtor da classe grita quais são suas dependências necessárias.
  2. Manutenibilidade: Alterar implementações de baixo nível não quebra a regra de negócio.
  3. 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”.

Compartilhar por e-mail

0 comentário(s)

Participe da conversa

Seu e-mail não será publicado.

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.

Um próximo passo para cada interesse.

Continue estudando com os livros, acompanhe as videoaulas ou consulte as soluções para o seu projeto.

Descubra mais sobre Régys Borges da Silveira

Assine agora mesmo para continuar lendo e ter acesso ao arquivo completo.

Continuar lendo