DevMedia - asp.net, Java, Delphi, SQL e web Design, tudo em um só lugar!
Bem vindo a DevMedia!
LOGIN:     SENHA:
 
 
DevWare  
Novidade: DevMedia lança o DevWare - Saiba mais!


  Este é um post disponível para assinantes MVP
Este post também está disponível para assinantes da ClubeDelphi DIGITAL
ou para quem possui Créditos DevMedia.  Clique aqui para saber mais!

POO - Dominando o uso de Interfaces - Revista Clube Delphi 134

O artigo trata sobre interfaces, que são contratos estabelecidos para que classes os sigam. No artigo será visto como criar, usar, trocar e delegar interfaces. Também serão vistos erros comuns a se evitar e as novidades do Delphi XE para o tratamento de interfaces.

[fechar]

Você não gostou da qualidade deste conteúdo?

(opcional) Você gostaria de comentar o que não lhe agradou?





ClubeDelphi 134

[Artigo disponível no Leitor Digital DevMedia. Clique aqui para acessá-lo]

> Clique aqui para ler todos os artigos da ClubeDelphi 134


Interface é uma palavra com múltiplos significados, tanto na física, na comunicação e na ciência da computação. No caso da ciência da computação o termo foi “emprestado” mais de uma vez, pois pode significar o tipo ou padrão de conexão entre dois componentes, o conjunto de elementos gráficos para comunicar um estado ou aceitar comandos de um usuário (GUI) e um contrato ou padrão entre dois objetos ou componentes.

No caso do hardware, por exemplo, USB é uma interface. É possível conectar qualquer tipo de dispositivo na porta USB de um computador contanto que o dispositivo siga certos padrões, ou seja, implemente a interface USB. O mesmo ocorre com PCI, PCI-express e outras tantas portas de conexão do computador.

Não é necessário ir muito longe: o TCP/IP fornece uma linguagem comum, acima dos protocolos físicos como ethernet, para que qualquer dispositivo em uma rede possa se comunicar em uma linguagem comum. O HTTP é um protocolo implementado sobre TCP/IP que recebe e envia dados através de requisições GET e POST e é a base de todas os sistemas online, redes sociais, webservices e computação em nuvem.

Qualquer objeto tem uma interface, mesmo que seja implícita, pois tem um conjunto de métodos e atributos públicos, que são visíveis e podem ser usados ou conectados com o meio exterior. A classe da Listagem 1, com seus métodos e atributos públicos e privados, tem uma interface implícita, que é o conjunto de seus membros públicos.

 

Nota: Todos os exemplos desse artigo foram feitos em um formulário com vários TButton, um para cada teste, e alguns métodos ou eventos. O código distribuído junto com esta edição tem mais exemplos além dos mostrados no artigo. O objetivo é que o leitor explore esse código modificando ou comentando partes para observar o que acontece.

 

 

Listagem 1. Classe de exemplo

  TForm1 = class(TForm)

  private

    { Private declarations }

  public

    { Public declarations }

  end;

 

  TUmaClasse = class

  private

    FUmaData: TDateTime;

    FUmNumero: integer;

    FUmNome: string;

    FUmaFracao: Double;

    FUmBooleano: boolean;

    procedure UmMetodoPrivado;

    property Data: TDateTime read FUmaData write FUmaData;

  public

    procedure SetNome(const value: string);

    function GetNome: string;

    procedure DigaAlo;

    property Nome: string read GetNome write SetNome;

  end;

 

 

Temos duas classes, um formulário e a classe TUmaClasse. Em TUmaClasse apenas SetNome, GetNome, DigaAlo e Nome são públicos, por isso eles definem uma interface implícita.

Em programação orientada a objetos quando se cria uma interface cria-se um contrato que deve ser respeitado por quem a implementa. A classe que implementa uma interface deve obrigatoriamente ter os métodos dela implementados.

Há uma certa similaridade com classes abstratas, mas a similaridade para por aí. É correto afirmar que uma classe totalmente abstrata não tem nenhum “comportamento” pois todo ele será criado nas classes derivadas, mas ainda assim uma classe derivada pode implementar alguns métodos e deixar outros ainda abstratos, para uma implementação futura. Adicionalmente uma classe abstrata ainda pode ter atributos públicos, ou propriedades, que podem ser herdados pelos seus descendentes. Uma classe totalmente abstrata não pode ser instanciada, mas uma classe que tem alguns métodos abstratos pode.

Além disso quando se começa uma família de classes a partir de uma classe abstrata todos os seus descendentes seguem uma herança linear, vertical. Usando interfaces esse cenário muda muito.

Para interfaces valem todas as regras de herança e polimorfismo, exceto que uma interface pode “herdar” de uma, duas ou mais interfaces. Quando uma interface herda de outra o termo técnico apropriado é “implementa” e não herda. O mesmo ocorre quando uma classe implementa uma interface.

Uma classe não pode ser descendente de uma interface, mas se for descendente de uma classe que implementa uma interface, ela e todos os seus descendentes passam automaticamente pela herança a implementar essa interface também.

"

A exibição deste artigo foi interrompida.

  Este é um post disponível para assinantes MVP
Este post também está disponível para assinantes da ClubeDelphi DIGITAL
ou para quem possui Créditos DevMedia.  Clique aqui para saber mais!


Vitor Luiz Rubio
Analista de Sistemas Sr. na Editora Revista dos Tribunais. Trabalha com Delphi desde a versão 3. Formado em Processamento de Dados pela FATEC-SP
O que você achou deste post?

    8 COMENTÁRIOS

[Fechar]

Este post é fechado - você precisa ter acesso ao post para incluir um comentário.



Fernanda Lopes
Tenho uma dúvida sobre a Matéria de Interface:

Fui tentar fazer exatamente como é mostrado nas listagens apresentadas para exemplos e dá erro quando tento compilar o código da 'listagem 5' da página 49, na seguinte parte:

-> if (aviao is TAviao) then
begin
ShowMessage('Avião é um TAviao');
(aviao as TAviao).DeslocarSe;
end;

O Delphi apresenta a seguinte mensagem de erro:
Operator not applicable to this operand type

Se alguém puder me ajudar me explicando o porquê disso, agradeço.
[há +1 ano] - Responder

 

Vitor Rubio
Fernanda, boa tarde.
Qual é a versão do seu Delphi? Algumas particularidades dos operadores "is" e "as" no sentido interface --> objeto passaram a funcionar apenas no Delphi XE.

Além disso a interface deve obrigatoriamente ter um GUID para que os operadores is e as funcionem com ela.
[há +1 ano] - Responder
 

Fernanda Lopes
Estou usando o Delphi 7 e usei também GUID.
[há +1 ano] - Responder
 

Vitor Rubio
Então Fernanda, esse código não funciona com Delphi 7, pois o Delphi 7 não aceita operadores IS e AS para conversões Interface --> Objeto, apenas para Objeto --> Interface.

O que você pode fazer é um typecast forçado, tipo:

var
o: TObject;
o := TObject(minhainterface);

//operações com O agora podem usar is e as

mas cuidado: a linha o := TObject(minhainterface) cria uma nova referencia a uma instância já existente que não será contada pelo contador de referencias da interface. Se algum método ou trecho de código atribuir nil à minhainterface zerando o contador de referências a instância será destruida antes da hora e "o" terá uma referência inválida. Faça testes para verificar leaks ou corrupção de memória com o Fast Memory Manager 4 e não use o método Free em "o".
[há +1 ano] - Responder
 

Fernanda Lopes
Obrigada, vou fazer os testes e qualquer coisa reportarei aqui.
[há +1 ano] - Responder
 

Softplan Planejamento E Sistemas Ltda
Fiquei com uma dúvida na seguinte afirmação:

"Na Listagem 5 a linha (UmObjetoQualquer as ICarro).DeslocarSe cria uma referência a uma interface Icarro durante a conversão que não existirá mais depois da conversão, depois da execução do método, o que fará com que o objeto UmObjetoQualquer seja destruído."

Fiz o teste aqui e o objeto ainda continua existindo. Não seria o correto?
[há +1 mês] - Responder

 

Vitor Rubio
Boa tarde "Softplan". Preciso ver um trecho do seu código e saber qual versão do Delphi você está usando, mas 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: (em www.vitorrubio.com.br/downloads/TesteInterfaces.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
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.
[há +1 mês] - Responder
 

Vitor Rubio
Na verdade eu cometi um erro aqui, além de ter me expressado mal. Não é quando você converte (objeto as Interface).Metodo que ocorrem erros, e sim quando você converte um objeto para uma interface passando a variável interface para um método.
Quando se chega ao final do método o contador de referencias da interface chega a zero, porque ele não conta com a referência armazenada na variável objeto FORA do escopo da função. Sendo assim o free é chamado e o objeto destruido prematuramente.
Isso acontece quando você faz algo como:

var
objAviao: TAviao;
objCarro: TCarro;
objBarco: TBarco;
UmObjetoQualquer: TInterfacedObject;

OutroVeiculo: IVeiculo;
OutroCarro: ICarro;
OutroBarco: IBarco;
UmSerVivo: ISerVivo;

begin

//cria os objetos e os mantem com referencia em uma variavel do tipo objeto
objaviao:= TAviao.Create;
objCarro:= TCarro.Create;
objBarco:= Tbarco.Create;
UmObjetoQualquer := TCarro.Create;

//não se deve fazer isso direto com os objetos a não ser que exista uma interface apontando para eles
//fazendo isso o contador de referencia pode zerar
FazVeiculoAndar(objaviao); //saindo do escopo dessa função o argumento atinge refcount zero, destruindo objaviao sem ligar para sua referencia objaviao fora da função
FazVeiculoAndar(objCarro); //saindo do escopo dessa função o argumento atinge refcount zero, destruindo objCarro sem ligar para sua referencia objCarro fora da função
FazVeiculoAndar(objBarco); //saindo do escopo dessa função o argumento atinge refcount zero, destruindo objBarco sem ligar para sua referencia objBarco fora da função

//a partir daqui os objetos não funcionam mais porque o contador de referência foi zerado enquanto o objeto foi
//convertido para interface
objAviao.Voar;
objCarro.Rodar;
objBarco.Velejar;

//a partir daqui o erro pode ocorrer a qualquer momento
(objAviao as IAviao).Voar;
(objCarro as ICarro).Rodar;
(objBarco as IBarco).Velejar;
(UmObjetoQualquer as ICarro).Rodar;


Mas o certo seria criar algo como:


var
aviao: IAviao;
carro: ICarro;
barco: IBarco;

OutroVeiculo: IVeiculo;

begin

//conversão objeto/interface
aviao := TAviao.Create;;
carro := TCarro.Create;
barco := Tbarco.Create;

//isso seria o correto
FazVeiculoAndar(aviao);
FazVeiculoAndar(carro);
FazVeiculoAndar(barco);

aviao.Voar;
carro.Rodar;
barco.Velejar;

No meu código original eu tentei mostrar em um mesmo botão a maneira certa e errada de se fazer isso, deixando parte do código comentado, e isso não ficou muito didático. Eu mesmo me enrolei depois de ler.
Separei esse código em 2 botões, sendo o antigo button3 o modo incorreto, e o novo button8 o método correto. O novo exemplo pode ser baixado em: http://www.vitorrubio.com.br/downloads/CD134_Interfaces_V2_Reformulado.zip
[há +1 mês] - Responder
 
Cursos relacionados
Publicidade
[Fechar]

Você precisa estar logado para dar um feedback.

Clique aqui para efetuar o login
[Fechar]


Este post está fechado. Saiba mais sobre a assinatura MVP!
[Fechar] Você precisa estar logado para dar seu feedback.

Clique aqui para efetuar o login

Caso não tenha um cadastro DevMedia, clique aqui para se cadastrar (gratuito)
web-03
DevMedia  |  Anuncie  |  Fale conosco
Hospedagem web por Porta 80 Web Hosting
2013 - Todos os Direitos Reservados a web-03