Amigos quero mostrar para vocês o que é, e como podemos utilizar as interfaces no Delphi de forma que facilite nossa difícil vida de programador.
O que é interface?
As interfaces foram introduzidas na linguagem Object Pascal na versão 3 do Dephi, passando a partir desse momento a ser trabalhada de forma nativa. Vocês não fazem idéia de quão significativo foi isso! Então alguns irão perguntar: "Mas como se vivia sem as interfaces antes disso ?" E eu respondo: Criando classes que não eram implementadas, mas que declaravam seus métodos para serem implementados pelas classes descendentes. Claro que você já esbarrou com um código desses, mas não deu atenção. Veja a declaração abaixo e me diga se você já não viu e talvez implementou algo parecido.
Type
TBrasileiro = class
public
function Trabalhar:Boolean; virtual; abstract;
function PagarImpostos:Boolean; virtual; abstract;
end;
O código acima define os métodos que a classe descendente deverá implementar. Podemos dessa maneira facilmente definir a nossa interface com base nos requisitos acima.
Type
IBrasileiro = Interface
['{31A22D9F-E857-4892-9D17-79FBE9AA7237}']
function Trabalhar:Boolean;
function PagarImpostos:Boolean;
end;
Dessa maneira podemos dizer que as interfaces são as "normas contratuais" que regem como nossa classe será definida e como deverá ser utilizada, tanto do lado do implementador como do lado do cliente. As classes podem implementar diversas interfaces, isso possibilita que uma classe "simule" a herança múltipla no Object Pascal.
Façamos agora duas observações quanto ao código acima!
Primeiramente vocês vão reparar que temos uma Sting enorme denominada de Globally Unique Identifier (GUID), no exemplo temos ['{31A22D9F-E857-4892-9D17-79FBE9AA7237}'] ), essa string pode ser gerada automaticamente no IDE do Delphi utilizando as teclas Ctrl+Shift+G, e servirá para identificar de forma única a nossa interface. Por enquanto é tudo que precisam saber sobre essa numeração.
Posteriormente você verá que não foi utilizado especificadores de visibilidade ( private, protected, public, lembra deles ?), isso porque todos os métodos declarados numa interface são públicos por default.
De onde vem ?
Sei que você já sabe disso, mas vou comentar mesmo assim: Todas as classes em Delphi descendem de TObject, isso de maneira implícita, da mesma forma as interfaces na linguagem object pascal descendem de forma implícita de IInterface, podemos ver sua definição na Unit System. Você verá a declaração de TInterfacedObject, falarei dessa classe mais a frente quando aparecer no nosso exemplo.
type
IInterface = interface
['{00000000-0000-0000-C000-000000000046}']
function QueryInterface(const IID: TGUID;
out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
IUnknown = IInterface;
{$M+}
IInvokable = interface(IInterface)
end;
{$M-}
{ TInterfacedObject provides a threadsafe default
implementation of IInterface.You should use TInterfaceObject
as the base class of objects implementing interfaces.; }
TInterfacedObject = class(TObject, IInterface)
protected
FRefCount: Integer;
function QueryInterface(const IID: TGUID;
out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
class function NewInstance: TObject; override;
property RefCount: Integer read FRefCount;
end;
Como é a vida com as interfaces?
Bom agora que você já sabe quando as interfaces ficaram disponíveis no Delphi, como era a vida antes dela, vamos ver como é a vida com elas. Vamos implementar duas interfaces simples, observe o exemplo abaixo.
ITrabalhador = Interface
['{31A22D9F-E857-4892-9D17-79FBE9AA7237}']
function Trabalhar:Boolean;
function PagarImpostos:Boolean;
end;
ISerHumano = interface
['{29D48E8C-EFF0-461C-9B9E-D5D7478E5B87}']
function AndarEreto:Boolean;
function Falar:Boolean;
procedure Ouvir;
procedure Correr;
end;
Basicamente podemos declarar nossa classe como:
TBrasileiro = class(TInterfacedObject, ITrabalhador,
ISerHumano);
Da maneira como está declarada a nossa classe acima, nós receberemos um código de erro do IDE, pois quando definimos que uma classe implementa uma interface, a classe deve contemplar todos os seus métodos implementando-os, se um ou mais de um não estiverem implementados receberemos um errro como esses abaixo:
[Pascal Error] Unit22.pas(33): E2003 Undeclared identifier: 'AndarEreto'
[Pascal Error] Unit22.pas(33): E2003 Undeclared identifier: 'Falar'
[Pascal Error] Unit22.pas(33): E2003 Undeclared identifier: 'Ouvir'
[Pascal Error] Unit22.pas(33): E2003 Undeclared identifier: 'Correr'
[Pascal Error] Unit22.pas(33): E2003 Undeclared identifier: 'Trabalhar'
[Pascal Error] Unit22.pas(33): E2003 Undeclared identifier: 'PagarImpostos'
Sua implementação é obrigatória nas classes que utilizam as interfaces, mas há uma maneira de postergar essa implementação na qual falarei mais abaixo. Você com certeza notou que nossa classe descende de TInterfacedObject, e o motivo é simples, essa classe já implementa de forma segura os métodos obrigatórios definidos em IInterface, ela descende de TObject e facilita o nosso trabalho para controlar o uso de interfaces. É extremamente recomendado o seu uso!
Nossa classe utilizando interfaces!
Para termos a correta declaração para a classe acima devemos ter um código conforme este:
TBrasileiro = class(TInterfacedObject, ITrabalhador,
ISerHumano)
public
// métodos da interface ITrabalhador
function Trabalhar:Boolean;
function PagarImpostos:Boolean;
// métodos da interface ISerHumano
function AndarEreto:Boolean;
function Falar:Boolean;
procedure Ouvir;
procedure Correr;
end;
Dessa forma estamos dizendo que a classe TBrasileiro descende de TInterfacedObject e que implementa os métodos “definidos” pelas interfaces ITrabalhador e ISerHumano.
Postergando a implementação da interface:
Na realidade o que vamos fazer é instruir ao compilador para que ele procure pela implementação da interface numa classe que está representada por uma propriedade em nossa classe TBrasileiro, vamos então ver o código:
TBrasileiro = class(TInterfacedObject, ITrabalhador,
ISerHumano)
public
// métodos da interface ISerHumano
function AndarEreto:Boolean;
function Falar:Boolean;
procedure Ouvir;
procedure Correr;
// postergando a implementacao da interface ITrabalhador
function GetTrabalhador:ITrabalhador;
property Trabalhador:ITrabalhador
read GetTrabalhador Implements ITrabalhador;
end;
Agora podemos receber na nossa propriedade Trabalhador, qualquer classe que implemente a interface ITrabalhador. Dessa forma deixamos a implementação desses métodos para outra classe compatível, que poderá trabalhar sendo chamada sob demanda aliviando assim o peso de uma instanciação desnecessária.
Implementando a interface ITrabalhador:
TProgramador = class(TInterfacedObject, ITrabalhador)
public
// métodos da interface ITrabalhador
function Trabalhar:Boolean;
function PagarImpostos:Boolean;
// métodos dessa classe
procedure Dormir;
procedure Acordar;
procedure Comer;
procedure SurfarNaNet;
procedure Programar;
end;
No nosso método GetTrabalhador podemos ter a chamada para uma instância da classe TProgramador, pois existe a compatibilidade gerada pela interface.
function TBrasileiro.GetTrabalhador: ITrabalhador;
begin
Result := (TProgramador.Create) as ITrabalhador;
end;
Outra pecuriaridade importante!
Ao utilizarmos interfaces nas nossas aplicações, devemos reparar que as interfaces são sempre inicializadas com o valor Nil. E são liberadas quando estão fora do escopo ou recebem novamente um valor Nil. Já sei o que você está pensando, se elas iniciam com Nil, e fora do escopo ou quando recebem nil são liberadas então posso utilizar interfaces para referências de objetos diminuindo os meus memory leaks! Isso mesmo podemos utilizar as interfaces para trabalhar nossos objetos, tornando mais segura a nossa aplicação. Mas … para tanto as interfaces que implementarmos devem descender obrigatoriamente em algum momento de TInterfacedObject, eu falei que é recomendado o seu uso, lembra? A particularidade de TInterfacedObject ser autosuficiente deve-se ao fato de que em sua implementação do método _Release e chamado o método Destroy quando o FRefCount chega a 0 (zero), você pode conferir no código na unit System.
Conclusão.
O uso de interfaces no projeto pode melhorar e muito a reusabilidade e flexibilidade de código no mesmo, seu uso nos obriga a ter uma estruturação mais refinada, forçando-nos a sermos mais organizados. Nos permite ainda aplicar o conceito de upCast ( Converter um objeto de um tipo mais especializado para um tipo mais genérico) e também simular a herança múltipla no Object Pascal, aumenta o desaclopamento de classes. Enfim você deve começar a utilizar esse conceito e assim daqui a pouco ele estará correndo nas suas veias e quando for escrever sua classe já irá querer extrair dela uma ou duas interfaces para que sejam amplamente utilizadas no seu projeto.
1 comment
Rapaz, esse blog foi inaugurado em Julho e só agora tomei conhecimento, sorte minha em você ter enviado um email para o grupo Delphi do Google, rs...
É como falei lá, parabéns por criar esse excelente blog, volto a navegar por aqui novamente e comentar ;)
Brother, da maneira como você redigiu fica fácil e divertido ler o assunto, gostei dessa parte:
Type
IBrasileiro = Interface
['{31A22D9F-E857-4892-9D17-79FBE9AA7237}']
function Trabalhar:Boolean;
function PagarImpostos:Boolean;
end;
PagarImpostos, *ái* daquele que não pagar impostos, rs...
Parabéns novamente pela revista digital (blog), já assinei (RSS) para acompanhar cada matéria :)
Grande abraço,
Silvio Clécio.
Postar um comentário