Recentemente me perguntaram no site da DevMedia se é correto ou não misturar interfaces e objetos em uma conversão temporária, como esta abaixo:
var C: TCarro;
begin
C := TCarro.Create; //tenho um objeto, mas C não conta referencias
C.Placa := 'ABC1234';
(C as ICarro).Rodar; //converte e roda na mesma linha, mas zera o contador de referencias na proxima linha
C.Parar; //isso dá erro
Segue links da Thread:
http://www.devmedia.com.br/poo-dominando-o-uso-de-interfaces-revista-clube-delphi-134/22569
http://www.vitorrubio.com.br/downloads/TesteInterfaces.zip
http://www.vitorrubio.com.br/downloads/CD134_Interfaces_V2_Reformulado.zip
Isso por causa do problema de se zerar o contador de referências em interfaces tendo mais uma referência ao objeto em uma variável do tipo objeto e destruir esse objeto prematuramente.
Basicamente nesses casos o comportamento esperado seria um erro.
Pode acontecer de o objeto continuar acessível na memória, ou pelo menos alguns métodos que não usem dados internos do objeto, mas esse comportamento não é o esperado e é causado simplesmente por "sujeira" na memória.
Fiz um teste aqui e também funcionou, mesmo assim eu não recomendo. Diferenças na versão do Delphi ou na plataforma podem ocasionar erros.
Na época em que escrevi esse artigo eu usava Delphi XE em um computador de 64 bits, mas não me lembro a versão do Windows.
Hoje fiz o teste tanto no Delphi XE como no Delphi XE 4 usando Windows 7, mas num computador de 32 bits.
O porquê de eu esperar que essa construção cause um erro eu explico:
Dado que interfaces contam referências, então são "Reference Counted" e dado que você cria, converte e usa um objeto dentro de um mesmo método (como o evento click de um botão).
Se você criar o seu objeto já atribuindo ele à uma variável do tipo interface, assim:
var i: ICarro;
begin
i:= TCarro.Create; //1 referencia a um TCarro
(i as ICarro).DeslocarSe; //2 referencias ao TCarro //não precisa disso, apenas mostro como funciona normalmente
i.OutroMetodo;
end; //final do método, 0 referencias ao mesmo TCarro, então o objeto "hospedado" em I é destruido com free.
É esperado que um objeto apresente erro nesse tipo de conversão principalmente quando ele possui outro objeto dentro. Considere um objeto que tenha um StringList como um de seus campos.
var i: TCarro;
begin
i:= TCarro.Create; //1 referencia a um TCarro, no entanto I é um objeto, portanto não conta referências
(i as ICarro).DeslocarSe; //1 referencias ao TCarro hospedado em I convertido para ICarro
i.OutroMetodo; //antes de chegar aqui já temos 0 referências (pois não existe mais a referência temporária que estava sendo usada na conversão para ICarro), portanto Free é acionado e o objeto em I é destruído "por engano". Aqui deve ocorrer um erro ao se executar OutroMetodo, pois o próprio objeto está destruído .
end;
Veja esse exemplo que eu criei no Delphi Xe 4, links:
http://www.devmedia.com.br/poo-dominando-o-uso-de-interfaces-revista-clube-delphi-134/22569
http://www.vitorrubio.com.br/downloads/TesteInterfaces.zip
http://www.vitorrubio.com.br/downloads/CD134_Interfaces_V2_Reformulado.zip
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
Button5: TButton;
Button6: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
procedure Button6Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
IVeiculo = interface (iinterface)
['{1D2F0F1D-60CC-47BE-AD22-4DA59D87C7CA}']
procedure DeslocarSe;
procedure Parar;
end;
IVeiculoTerrestre = interface (IVeiculo)
['{CD718BB9-FE3A-4C19-8CE2-01EBB1843B96}']
procedure DeslocarSe;
procedure Parar;
procedure Rodar;
end;
ICarro = interface (IVeiculoTerrestre)
['{3B167780-8EA0-4F59-9DBF-A9F68F54F595}']
procedure DeslocarSe;
procedure Parar;
procedure Rodar;
end;
//TCarro = class(TInterfacedObject, ICarro) //nessa forma você pode atribuir a criação de um TCarro a uma variável ICarro, IVeiculoTerrestre e IVeiculo, mas não pode fazer conversões do tipo AS entre eles
TCarro = class(TInterfacedObject, ICarro, IVeiculoTerrestre, IVeiculo) //esta forma permite qualquer tipo de atribuição e conversão.
private
FPlaca: TStringList;
function GetPlaca: string;
procedure SetPlaca(value: string);
public
constructor Create;
destructor Destroy; override;
procedure DeslocarSe;
procedure Parar;
procedure Rodar;
property Placa: string read GetPlaca write SetPlaca;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TCarro }
constructor TCarro.Create;
begin
FPlaca := TStringList.Create;
end;
procedure TCarro.DeslocarSe;
begin
ShowMessage(Placa + ' Deslocando-se. ' + self.RefCount.ToString() + ' referências');
end;
destructor TCarro.Destroy;
begin
FPlaca.Free;
inherited;
end;
function TCarro.GetPlaca: string;
begin
Result := FPlaca.Text;
end;
procedure TCarro.Parar;
begin
ShowMessage(Placa + ' Parando.' + self.RefCount.ToString() + ' referências');
end;
procedure TCarro.Rodar;
begin
DeslocarSe;
end;
procedure TCarro.SetPlaca(value: string);
begin
FPlaca.Text := value;
end;
procedure TForm1.Button1Click(Sender: TObject);
var C: TCarro;
begin
C := TCarro.Create; //tenho um objeto, mas C não conta referencias
C.Placa := 'ABC1234';
(C as ICarro).Rodar; //converte e roda na mesma linha, mas zera o contador de referencias na proxima linha
C.Parar; //isso dá erro
C.Rodar;
C.Parar;
end;
procedure TForm1.Button2Click(Sender: TObject);
var C: TCarro;
I: IVeiculoTerrestre;
begin
C := TCarro.Create; //tenho um objeto, mas C não conta referencias
C.Placa := 'ABC1234';
I := (C as ICarro); //Conversão joga para I o controle da contagem de referencias
I.Rodar; //uso normal
I.Parar; //uso normal
I.Rodar; //uso normal
I.Parar; //uso normal
C.Rodar; //Funciona normal, mas não se deve misturar objetos comuns com interfaces, por isso para de usar C uma vez convertido para I
C.Parar; //Funciona normal, mas não se deve misturar objetos comuns com interfaces, por isso para de usar C uma vez convertido para I
end;
procedure TForm1.Button3Click(Sender: TObject);
var C: TCarro;
I: IVeiculo;
begin
C := TCarro.Create; //tenho um objeto, mas C não conta referencias
C.Placa := 'ABC1234';
I := (C as ICarro); //Conversão joga para I o controle da contagem de referencias
I.DeslocarSe;
I.Parar;
//I agora pode ser convertido para qualquer uma das interfaces ancestrais de ICarro sem perder o controle de referência
(I as ICarro).Rodar;
(I as ICarro).Parar;
(I as IVeiculoTerrestre).Rodar;
(I as IVeiculoTerrestre).Parar;
(I as IVeiculo).DeslocarSe;
(I as IVeiculo).Parar;
C.Rodar; //Funciona normal, mas não se deve misturar objetos comuns com interfaces, por isso para de usar C uma vez convertido para I
C.Parar; //Funciona normal, mas não se deve misturar objetos comuns com interfaces, por isso para de usar C uma vez convertido para I
end;
procedure TForm1.Button4Click(Sender: TObject);
var C: TCarro;
iv1, iv2: IVeiculo;
ivt1, ivt2: IVeiculoTerrestre;
ic: ICarro;
begin
C := TCarro.Create;
C.Placa := 'ABC1234';
ic := (C as ICarro);
ivt1 := (C as IVeiculoTerrestre);
iv1 := (C as IVeiculo);
ivt2 := (ic as IVeiculoTerrestre);
iv2 := (ic as IVeiculo);
ic.DeslocarSe;
ic.Parar;
ivt1.Rodar;
ivt1.Parar;
iv1.DeslocarSe;
iv1.Parar;
ivt2.Rodar;
ivt2.Parar;
iv2.DeslocarSe;
iv2.Parar;
end;
procedure TForm1.Button5Click(Sender: TObject);
var C: TCarro;
iv1, iv2: IVeiculo;
ivt1, ivt2: IVeiculoTerrestre;
ic: ICarro;
begin
C := TCarro.Create;
C.Placa := 'ABC1234';
ic := (C as ICarro);
ic.DeslocarSe;
ic.Parar;
if Supports(c, IVeiculoTerrestre, ivt1) then
begin
ivt1.Rodar;
ivt1.Parar;
end;
if Supports(c, IVeiculo, iv1) then
begin
iv1.DeslocarSe;
iv1.Parar;
end;
if Supports(ic, IVeiculoTerrestre, ivt2) then
begin
ivt2.Rodar;
ivt2.Parar;
end;
if Supports(ic, IVeiculo, iv2) then
begin
iv2.DeslocarSe;
iv2.Parar;
end;
end;
procedure TForm1.Button6Click(Sender: TObject);
var C: TCarro;
iv1, iv2: IVeiculo;
ivt1, ivt2: IVeiculoTerrestre;
ic: ICarro;
begin
C := TCarro.Create;
C.Placa := 'ABC1234';
ic := ICarro(C);
ivt1 := IVeiculoTerrestre(C);
iv1 := IVeiculo(C);
ivt2 := IVeiculoTerrestre(ic);
iv2 := IVeiculo(ic);
ic.DeslocarSe;
ic.Parar;
ivt1.Rodar;
ivt1.Parar;
iv1.DeslocarSe;
iv1.Parar;
ivt2.Rodar;
ivt2.Parar;
iv2.DeslocarSe;
iv2.Parar;
end;
end.
Ele não deu erro como eu esperava no primeiro botão, mesmo assim eu não aconselho a fazer um uso de interfaces + objetos como aquele, principalmente quando seus objetos são passados para métodos ou retornados deles.
Infelizmente não consegui reproduzir esse erro hoje. Compilei e rodei esse código no Delphi XE, no Delphi XE4 e no Lazarus, e em todos os casos funcionou.
Mesmo assim, cuidado redobrado ao se passar variáveis do tipo interface como parâmetros.
Comentários
Postar um comentário