2 de setembro de 2009

Usando interfaces em Delphi - parte 3

Vou montar neste post um exemplo do uso de interfaces em Delphi, mostrando as formas que encontrei para evitar as armadilhas apontadas no post anterior. A ideia é modelar uma classe que seja capaz de ordernar uma lista sem se preocupar com qual é de fato o tipo do objeto que está sendo classificado - basta que ele implemente uma interface apropriada, que chamarei aqui de IWSortable.

O primeiro ponto é a necessidade de referenciar o objeto original que foi atribuído à interface - lembre-se que ponteiro para interface é incompatível com ponteiro para class. No caso específico do IWSortable, a função de comparação poderá fazer uso de qualquer das propriedades de uma classe de objetos para determinar a ordem correta de cada um dentro da lista. Se for trabalhar, por exemplo, com uma lista de Pessoas, a ordem poderá ser primeiro por nome, depois por idade e envolver quantos campos mais forem necessários.

Minha solução para isso é criar uma interface base com uma função que retorne o ponteiro para o objeto atual. Todas as minhas interfaces, então, herdarão dessa base:
IWBaseInterface = interface
['{97DF568E-D4ED-4F1A-99DF-07E524BD598D}']
function GetSelfObj : TObject;
end;

IWSortable = interface(IWBaseInterface)
['{7CEF3AE4-9127-460E-B1A1-8D18B7CA7D05}']
function CompareTo (AObj : IWSortable) : integer;
end;
Com isso, todas as classes que implementarem a interface serão capazes de reportar seu próprio ponteiro quando solicitadas. Para facilitar, também criei uma classe base para fornecer essa funcionalidade como padrão.
TWInterfacedObject = class(TObject, IWBaseInterface)
{...}
public
function GetSelfObj : TObject;
end;
{...}
function TWInterfacedObject.GetSelfObj : TObject;
begin
Result := Self;
end;
A função precisa apenas reportar o ponteiro Self para satisfazer a interface IWBaseInterface. Uma classe hipotética TWPessoa poderia implementar o método CompareTo recorrendo a esta função :
TWPessoa = class(TWInterfacedObject, IWSortable)
public
idade : integer;
nome : String;
function CompareTo (AObj : IWSortable) : integer;
end;
{...}
function TWPessoa.CompareTo (AObj : IWSortable) : integer;
var pessoa : TWPessoa;
begin
Result := 0;
pessoa := AObj.GetSelfObj As TWPessoa;

if (nome < pessoa.nome) then
result := -1;
if (nome > pessoa.nome) then
result := 1;

// se os nomes são iguais, ordena pela idade
if (Result = 0) then
Result := idade - pessoa.idade;
end;
Veja que TWPessoa herda de TWInterfacedObject - que fornece a função GetSelfObj - e também implementa a interface IWSortable, o quê exige a presença da função CompareTo para comparar 2 objetos. Veja também que esta função faz a comparação usando mais de um campo da classe pessoa - justamente porque consegue obter o ponteiro para a Pessoa original a partir da interface informada no parâmetro AObj.

Felizmente, a versão 2010 do Delphi lançada recentemente pela Embarcadero resolve nativamente esse problema. Nesta versão, ponteiros para interfaces passam a ser compatíveis com ponteiros para classes. Veja o post de Malcon Groves sobre o assunto aqui. Para versões antigas, a interface base ainda terá que ser usada.

O segundo questão a resolver é o fato das interfaces Delphi terem suas referências contadas (veja o post anterior). Depois que um objeto é atribuído a uma variável do tipo interface, o controle do ciclo de vida do objeto passa a ser feito automaticamente, significando que ele pode ser destruído antes do que você imagina. Como todo o resto no Delphi para Win32 tem ciclo controlado pelo programador, acredito que misturar os dois mecanismos traz mais confusão do que benefícios. Além de ser mais sujeito a erros.

Aproveitei então a classe base TWInterfacedObject e implementei nela os requisitos exigidos pelo tratamento de interfaces do Delphi. Isto é, escrevi minha versão para as funções do IUnknown de forma que que a contagem de referências é sempre 1, me deixando livre para destruir os objetos no ponto que eu julgar mais conveniente. A classe ficou como segue:
TWInterfacedObject = class(TObject, IWBaseInterface)
protected
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
function GetSelfObj : TObject;
end;
{...}
function TWInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;

function TWInterfacedObject._AddRef: Integer;
begin
Result := 1;
end;

function TWInterfacedObject._Release: Integer;
begin
Result := 1;
end;
Essas soluções apresentadas aqui funcionam bem se você pretende usar interfaces sem qualquer vínculo com a tecnologia COM. Caso contrário, é preferível utilizar os mecanismos padrões do Delphi pois eles foram construídos justamente para fornecer essa interação.

Mais Informações
Usando interfaces em Delphi - parte 1 e parte 2.

Nenhum comentário :

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.