Consumir uma implementação por interface sem misturar referências e destruição manual
Tutorial · prática orientada · IntermediárioClasses, métodos e units
Unit principal compilada e exercitada com DCC32 37.0, build 37.0.59082.6021. Referência editorial: Delphi 11.3 e 13.2; essas duas versões não foram verificadas individualmente.Documentação, instalação e limites de Fundamentos em seis units
Uma interface é útil quando um consumidor precisa de um comportamento e não precisa conhecer os detalhes de quem o executa. Isso não significa criar uma interface para cada classe. O contrato deve representar uma fronteira que faça sentido no projeto.
Vamos usar uma dependência simples: um objeto que fornece uma saudação. O exemplo evita banco de dados e rede para concentrar a atenção no contrato e no ciclo de vida.
Declare o comportamento necessário
Crie a unit Exemplo.Saudacao.pas. O consumidor receberá ISaudacao, e a implementação concreta ficará em TSaudacaoSimples.
unit Exemplo.Saudacao;
interface
type
ISaudacao = interface
['{E3C2842B-67ED-4BE2-AB9A-9A715BAE8E10}']
function Texto(const Nome: string): string;
end;
TSaudacaoSimples = class(TInterfacedObject, ISaudacao)
public
function Texto(const Nome: string): string;
end;
function Cumprimentar(const Provedor: ISaudacao;
const Nome: string): string;
implementation
uses
System.SysUtils;
function TSaudacaoSimples.Texto(const Nome: string): string;
begin
Result := 'Olá, ' + Trim(Nome) + '!';
end;
function Cumprimentar(const Provedor: ISaudacao;
const Nome: string): string;
begin
if not Assigned(Provedor) then
raise EArgumentException.Create('Provedor não informado');
Result := Provedor.Texto(Nome);
end;
end.A função Cumprimentar não cria a dependência. Ela recebe uma implementação já escolhida. Isso permite alterar a saudação sem modificar o consumidor.
Consuma por uma referência de interface
No projeto de console, declare uma variável Provedor: ISaudacao e utilize:
Provedor := TSaudacaoSimples.Create;
Writeln(Cumprimentar(Provedor, 'Ana'));
Provedor := nil;A implementação padrão de TInterfacedObject utiliza contagem de referências. Nesse modelo, o objeto é destruído quando sua última referência de interface é liberada. Isso não é uma propriedade universal de toda classe que implementa uma interface: existem implementações com outro modelo de duração. Veja Interface References.
Não chame Free no objeto enquanto referências gerenciadas de interface continuam usando-o. Também não assuma que um componente com proprietário segue automaticamente o mesmo modelo de TInterfacedObject.
O contrato não elimina decisões
O que deve acontecer quando Nome está vazio? Nesta implementação, a saudação fica sem nome. Isso precisa ser discutido pelo contrato: talvez o consumidor deva exigir uma entrada válida ou o provedor deva oferecer uma saudação genérica.
Uma interface organiza métodos; ela não documenta sozinha regras, falhas, concorrência ou propriedade dos objetos devolvidos. Registre esses comportamentos quando forem relevantes.
Mantenha também a direção da dependência: o provedor não deve precisar de um formulário para montar o texto. A apresentação pode exibir o resultado do contrato em qualquer controle.
Substitua antes de abstrair mais
Crie TSaudacaoFormal, também implementando ISaudacao, e faça Texto retornar outra mensagem. Passe essa implementação para a mesma função Cumprimentar. Se você precisou alterar a assinatura da função, o contrato talvez estivesse acoplado à primeira implementação.
Em um sistema real, contratos semelhantes podem representar envio de notificações ou leitura de dados. Não generalize a interface de saudação para esses problemas: modele cada necessidade com as entradas e os resultados apropriados.
Erros que merecem atenção
Referências de interface que apontam umas para as outras podem formar ciclos e impedir que a contagem chegue a zero. Planeje a direção das referências em vez de esperar que a contagem resolva todos os grafos de objetos.
Outro erro é devolver objetos cuja duração não está clara. Se um método entrega uma instância para o chamador, documente quem é responsável por liberá-la.
Verifique o exemplo e avance
Compile a unit e teste saudação simples, formal e provedor nil. A saída esperada de nil é uma EArgumentException; os demais resultados dependem da implementação escolhida. A unit completa foi compilada; os casos da saudação e do provedor ausente passaram no projeto de verificações.
Continue em Separando tela, serviço e domínio para colocar contratos em uma fronteira de aplicação.
Segunda implementação, mesmo consumidor
- Abra a variante TSaudacaoFormal na revisão dos fundamentos.
- Compare a mesma assinatura ISaudacao com TSaudacaoSimples.
- Passe a variante a Cumprimentar sem alterar a chamada.
- Confira ausência de provedor e resultado da saudação formal no runner.
Criação → referência ISaudacao → consumidor → última referência liberada → destruição. Não combine Free manual com vida controlada por TInterfacedObject. A variante preserva o consumidor; o modelo de contagem não descreve todo ownership Delphi.
