Atenção: esse artigo tem um vídeo complementar. Clique e assista!
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.
Em que situação o tema é útil
Interfaces criam pontos de conexão entre partes de um sistema e ajudam a isolar do mundo exterior os detalhes de implementação de uma classe. Devem ser usadas sempre que se necessitam usar vários objetos semelhantes, mas que não são da mesma família, evitar ou corrigir o alto acoplamento, dividir tarefas entre programadores ou até mesmo entre consultorias terceirizadas e programar por “contratos”. Elas são importantíssimas para criar sistemas com baixo nível de acoplamento e maior facilidade de manutenção. Programando-se orientado a interfaces facilita a distribuição das tarefas entre programadores e a criação de testes unitários. Além disso qualquer objeto pode ser substituído por qualquer outro que implemente a mesma interface. Isso proporciona um segundo nível de flexibilidade, pois permite que objetos de hierarquias distintas tenham algo em comum e possam ser intercambiados.
Interfaces
Interfaces definem contratos ou pontos de conexão entre objetos. Se um método aceita como parâmetro uma interface A isso significa que ele aceita qualquer objeto que implemente essa interface. Da mesma forma um objeto que implemente uma interface B sempre pode ser substituído por qualquer objeto que implemente a mesma interface. Com o uso de interfaces é possível dizer de antemão qual é o comportamento esperado de um objeto e que métodos ele deve ter, forçando-o a cumprir esse contrato. Um gerente de projeto (ou coach ou scrum master dependendo da sua metodologia) pode dividir organizadamente tarefas entre programadores extraindo de cada estória de usuário, caso de uso, cartão CRC ou mesmo dos requisitos o comportamento básico de qualquer entidades. Até mesmo para a contratação de uma consultoria terceirizada é possível entregar uma interface e pedir: “quero uma classe ou componente que implemente essa interface”. O “Como” a interface é implementada é “problema” de quem a implementa e o “que” se faz com ela é “problema” de quem a usa. Ao longo do artigo serão mostrados comparação, conversão implícita e explícita, operações com interfaces e alguns “minissistemas”.
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.
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.
Como múltiplas interfaces podem ser implementadas por uma classe ou interface é possível usar esse mecanismo para simular herança múltipla, fazendo com que todos os métodos de várias interfaces se combinem em uma única. Se a implementação das interfaces forem delegadas a objetos que já as implementem então tem-se herança múltipla “quase real” e com pouquíssimas linhas de código.
Um outro grande benefício das interfaces é que elas tem uma contagem de referências interna. Isso significa que elas não precisam ser destruídas, pois se auto destroem quando nenhum outro objeto precisa delas. Isso é o mais próximo que temos de um garbage collector.
Quando se programa orientado a interfaces automaticamente protege-se o software de uma dependência mútua de detalhes de implementação. Isso é um reforço para o conceito de encapsulamento, ou “caixa preta”, pois um objeto que implementa uma interface pode ser substituído por qualquer outro que também implemente essa interface sem que o código pare de funcionar, mesmo que o comportamento do outro objeto seja totalmente diferente. Ou seja, se um objeto A implementa uma interface sua implementação deve ser isolada de qualquer objeto B que dependa (seja cliente, use) A. Desse modo, B usa apenas a interface de A, e qualquer alteração em A ou B pode ser feita contanto que esse contrato continue sendo respeitado.
...