Você está na página 1de 6

Desmistificando as Interfaces

Facebook Twitter

(16) (0)

Veja neste artigo de Gustavo Chaurais, como trabalhar com Interfaces de uma maneira
simples.
Parte I
Muitos já ouviram falar, outros sabem que existe mas nunca utilizaram... o fato é que poucos desenvolvedores fazem uso efetivo de
interfaces em seus projetos. Este recurso, que foi introduzido no Delphi 3 para dar suporte à arquitetura COM, pode melhorar
substancialmente o design de uma aplicação orientada a objetos. Além disso, apesar de não parecer, é muito simples e fácil de ser
implementado.

Mas o que são interfaces?

Pense na primeira imagem que vem à sua mente quando falamos esta palavra: interface. Certamente, existe uma grande
possibilidade de você ter pensado em uma janela/formulário para a interação com os usuários de seu sistema. Contudo, podemos ir
mais a fundo e também imaginar os objetos de nossa aplicação interagindo uns com os outros. Estes, por sua vez, precisam se
conhecer para então saber que mensagens poderão trocar. É aí que entram as interfaces.

Podemos visualizar os objetos de nossa aplicação como instâncias de uma classe na memória e dividi-los em duas partes: sua
visualização externa e a própria implementação de seus métodos.

Note que até o momento estamos focando nossas atenções apenas nesse contexto. Entretanto, interfaces podem ser encontradas em
diversas outras situações. Além das já citadas interações com usuário e entre objetos, programas inteiros também podem se
comunicar e peças de hardware precisam se encaixar perfeitamente. Com isso, fica fácil imaginar diversas situações para o uso de
interfaces.

Vamos então conceitualizá-las como um contrato entre duas partes. Se determinado objeto declara que implementa uma interface,
ele a deve seguir à risca, implementando tudo o que for estipulado por este “acordo”. Da mesma maneira, os outros objetos poderão
ter certeza de que as mensagens enviadas a ele serão correspondidas. E o mais importante: saberão quais operações poderão ser
solicitadas.

Tente fazer uma relação desses objetos com um programa e sua interface gráfica. A intenção do desenho de um formulário é a de
mostrar quais operações podem ser executadas sobre tal aplicação. Expondo-as de maneira clara para quem estiver utilizando o
sistema.

E quais as vantagens do uso desse conceito em meus objetos?

O Delphi, por natureza, é uma linguagem fortemente tipada. Isso significa que, quando um método é chamado, já sabemos
previamente quais os seus parâmetros e seu tipo de retorno. No caso de objetos, a declaração desses métodos é feita na sua classe,
em uma seção denomida, coincidentemente, de interface.

Se declararmos uma variável em nosso código cujo tipo é uma classe, estaremos certos de que poderemos chamar qualquer método
disponibilizado pelo objeto que a ela for associado.

UmCiclista: TCiclista;

(...)
UmCiclista := TCiclista.Create;

UmCiclista.Pedala;

Como podemos ver, a variável UmCiclista recebe uma instância da classe TCiclista e chama seu procedimento Pedala. No entanto, o
que poderia ser feito se precisássemos associar à UmCiclista, um objeto de uma classe TTriatleta? Bom, se você fosse um
programador C++ responderia rápido: fácil, projete TTriatleta para herdar de 3 classes: TCorredor, TCiclista e TNadador. Tudo bem,
uma solução adequada para este caso. Como UmCiclista só aceita objetos que implementem TCiclista, o TTriatleta seria aceito sem
problemas, pois herda de TCiclista ou, em outras palavras, todo triatleta também é um ciclista. Entretando, caro leitor, não possuímos
o conceito de Herança Múltipla na nossa Delphi Language. Uma classe pode herdar somente de uma outra. Mas então como
resolveríamos esse caso? Veremos mais a frente como utilizar as interfaces para suprir essa necessidade.

Ok, chega de teoria! Que tal partimos logo para o código?

Uma interface é constituída apenas por declarações de métodos. Sua diferença em relação a uma classe comum é que nela não
haverá nenhuma implementação.

ICiclista = interface

procedure SetBicicleta(Bicicleta: TBicicleta);

function GetBicicleta: TBicicleta;

procedure Pedala;

end;

Ou seja, declarar uma classe que implementa ICiclista significa dizer que todos os métodos dessa interface estarão presentes. Note
ainda, que a declaração dos métodos na interface é feita da mesma forma que em uma classe, com a exceção da visibilidade (public,
protected, private...). E, da mesma maneira que utilizamos a letra T para denotarmos tipos, usaremos a letra I para iniciarmos
interfaces, apenas por convenção.

Vejamos agora a declaração de uma classe que implementa ICiclista.

TCiclista = class(TInterfacedObject, ICiclista)

private

(...)

public

//Métodos de ICiclista

procedure SetBicicleta(Bicicleta: TBicicleta);

function GetBicicleta: TBicicleta;

procedure Pedala;

end;
Como podemos ver, a classe TCiclista poderia ter ainda diversos outros métodos próprios. Todavia, os declarados em ICiclista
obrigatoriamente devem ser implementados (ou declarados como abstratos para posterior implementação). Agora, as operações de
ICiclista estarão presentes em TCiclista e podem ser chamadas como outras quaisquer.

Neste ponto, já podemos apontar uma grande utilidade para esse conceito. Imagine a implementação de um grande sistema. Quando
dois programadores ou pares iniciam a codificação de módulos separados e interligados, eles podem estabelecer previamente um
“contrato” de comunicação. Deste modo, quem programa o módulo A, saberá que o módulo B implementa a interface X, portanto,
terá certeza das mensagens que poderão ser enviadas. E mais importante do que isso, terá código compilável e receberá ajuda do
tipo code insight ao manipular os fontes.

Como sabemos, toda classe em Object Pascal acabará herdando de uma outra conhecida como TObject. Deste modo, os métodos
desta classe estarão disponíveis a qualquer outra que for herdada. Não surpreendentemente, o mesmo acontece com as interfaces.
Toda e qualquer interface no Delphi, mesmo que não seja declarado explicitamente, herdará de IInterface. Mais uma vez, a diferença
na herança entre classes e interfaces está na falta de implementação. Se uma interface X herda de Y, um objeto que implemente X
também deverá implementar os métodos de Y. Ou seja, se seguirmos essa idéia até o topo da hierarquia, todos os objetos que
implementem interfaces, deverão definir implementações para os métodos de IInterface.

Se você for do tipo que repara em tudo, deve ter notado que a nossa classe TCiclista não herda diretamente de TObject. Deve
também ter notado que não estamos implementando nenhum método de IInterface (mesmo que ainda não saibamos quais eles
sejam). A declaração de uma classe tem a seguinte forma: TClasse = class(TClasseMãe, IInterfacesImplementadas). Vamos agora dar
uma boa olhada nas implementações de IInterface e TInterfacedObject.

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;

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;

Perceba que TInterfacedObject já faz o trabalho de declarar e codificar os métodos de IInterface. Sendo assim, tais métodos já foram
implementados para qualquer classe que a herde, o que nos livra do trabalho da implementação a cada nova declaração. É altamente
aconselhável que sempre usemos essa classe para implementar interfaces.

Mas o que há de tão especial em IInterface? Observamos um método para consulta e dois para a contagem de referências. Um tópico
muito importante é que essa contagem inibe a necessidade de executarmos um destrutor para estes objetos. Ou seja, nunca
precisaremos (e nem deveremos) chamar o método Free de um objeto que utiliza uma interface. Isso funciona da seguinte maneira:
a cada nova referência ao objeto, o método _AddRef é chamado, o que incrementa o valor de RefCount de um TInterfacedObject. Da
mesma forma, a cada perda de referência, o método _Release é executado e a contagem é decrementada. Quando o contador chegar
a 0, o objeto é liberado da memória.

UmCiclista := TCiclista.Create; //Referências ao objeto criado: 1.

AqueleCiclista := UmCiclista; //Referências ao mesmo objeto: 2.

AqueleCiclista.Pedala;

UmCiclista := nil; //Perdeu a referência, total: 1.

AqueleCiclista := nil; //Nenhuma referência ao objeto. Retirado da memória.

E o problema do Triatleta?

Até o momento, a única utilidade prática das interfaces está na garantia de que determinados métodos estão presentes nas classes.
Vamos agora partir para um exemplo mais concreto. Além de ICiclista, vamos declarar:

ICorredor = interface

procedure SetTenis(Tenis: TTenis);

function GetTenis: TTenis;

procedure Corre;

end;

INadador = interface

procedure SetTouca(Touca: TTouca);

function GetTouca: TTouca;

procedure Nada;

end;

E finalmente, vamos especificar TTriatleta:


TTriatleta = class(TInterfacedObject, ICorredor, ICiclista, INadador)

private

(...)

public

procedure Para;

procedure Descansa;

//Métodos de ICorredor

procedure SetTenis(Tenis: TTenis);

function GetTenis: TTenis;

procedure Corre;

//Métodos de ICiclista

procedure SetBicicleta(Bicicleta: TBicicleta);

function GetBicicleta: TBicicleta;

procedure Pedala;

//Métodos de INadador

procedure SetTouca(Touca: TTouca);

function GetTouca: TTouca;

procedure Nada;

end;

Implemente os métodos e tente compilar. Você não receberá nenhuma mensagem de erro, nem sequer um hint. Veja que
conseguimos driblar a falta da herança múltipla apenas com o uso de interfaces. Este é um dos principais pontos de uso do conceito
abordado, que está presente em quase todas as linguagens que nos disponibilizam a programação orientada a objetos.
Diferentemente da herança múltipla, que é muito difícil de ser implementada e controlada.

Existe uma outra maneira de declararmos uma variável:

UmCiclista: ICiclista;

E é nesse ponto que as interfaces fazem uma grande diferença. Em outra parte de nosso código poderíamos ter:
UmCiclista := TCiclista.Create;

UmCiclista.Pedala;

Ou então:

UmCiclista := TTriatleta.Create;

UmCiclista.Pedala;

Isso acontece porque a variável UmCiclista foi declarada como sendo do tipo ICiclista, ou seja, essa pode receber objetos de qualquer
classe, desde que implementem ICiclista. Em uma situação real, poderíamos ter instâncias que herdem de classes completamente
diferentes sendo armazenados em uma mesma variável. Por exemplo, existem casos em que não é conceitualmente correto mexer na
hierarquia apenas para que objetos possam ser associados uns aos outros. Nestes casos, as interfaces cairiam como uma luva, pois
serviriam como um encaixe de classes.

Entretanto, como na herança comum, se temos uma variável declarada de um tipo TAtleta e fazemos com que ela receba um objeto
de uma classe TCiclista (que descende de TAtleta), os únicos métodos disponíveis serão os de TAtleta, e não os de TCiclista. Por isso,
o código a seguir não seria compilado:

UmCiclista := TTriatleta.Create;

UmCiclista.Nada;

Apesar de a classe TTriatleta possuir um método Nada (oriundo de INadador), a variável UmCiclista foi declarada como sendo do tipo
ICiclista, o qual não possui tal operação. O mesmo código não seria compilado se tentássemos chamar o método Descansa.

Conclusão

Um projeto de sistema orientado a objetos pode apresentar muitos pontos complexos a serem tratados. Como vimos, as interfaces
nos permitem muita flexibilidade tanto na hora do design como da implementação.

Nos próximos artigos, abordaremos mais a fundo: identificação, type casts, resolução de duplicidades e delegações. Porém, não é
preciso esperar para começar a usufruir de todas as vantagens do uso de interfaces desde já!

por Gustavo Chaurais (16) (0)

Ficou com alguma dúvida?