Desmistificando as Interfaces no Delphi
Escrito por Carlos B. Feitoza Filho | |
Categoria: Artigos | |
Categoria Pai: Addicted 2 Delphi! | |
Acessos: 34370 |
Até pouco tempo atrás eu não entendia as interfaces. Eu lia a respeito e não conseguia ver onde eu poderia usá-las. De fato, vivi até hoje e ainda vivo sem precisar usá-las não mais que uma vez perdida, mas calma, isso não as torna inúteis.
Assim como qualquer coisa nessa vida, você não é obrigado a usar nenhuma técnica específica apenas porque alguém te falou que era genial. Você também não precisa usar alguma coisa porque ela está na moda, aliás, esse é o pior caso. É por isso que eu quero deixar aqui um pensamento que você, caro leitor, pode usar não apenas no contexto computacional, mas também na sua vida de uma forma geral:
A necessidade é o motorista que o conduz na estrada da resolução de um problema
Depois dessa tentativa frustrada de ser intelectual eu me sinto no dever de falar de forma clara que você só precisa usar interfaces se ela for a única forma de conseguir resolver um problema ou se você quiser manter seu código mais organizado em alguma situação MUITO ESPECÍFICA. Ignore o tom drástico da frase anterior, trabalhar com interfaces nem sempre é complicado, mas seu uso desnecessário pode tornar seu código complexo sem necessidade, algo que evito a todo custo. Sou defensor ferrenho do KISS Principle ;) E por falar nisso, chega de conversa fiada vamos ao que interessa.
Para que serve uma interface?[1]
Uma interface tem basicamente duas utilidades, as quais devem ser observadas antes de se decidir por utilizá-las. São elas:
- Obrigar a implementação de métodos. Usando a terminologia mais difundida, "obrigar a implementação" significa estabelecer uma espécie de "contrato", no qual uma classe se compromete a implementar todos os métodos da interface. Não é à toa que o termo correto utilizado entre classes e interfaces é "A classe implementa a(s) interface(s)", isso é absolutamente correto, já que, todos os métodos da interface precisam ser implementados OBRIGATORIAMENTE na classe que a implementa.
- Introduzir comportamentos adicionais a uma classe. Normalmente ao trabalhar com orientação a objetos, tudo é muito simples; uma classe herda de outra, que herda de outra, que herda de outra e assim sucessivamente. Você vive feliz com isso até perceber que vai precisar implementar uma classe que vai ter comportamentos conjuntos de duas outras classes distintas, classes que não pertencem a uma mesma hierarquia. No Delphi não existe herança múltipla, logo, ao usar interfaces é possível separar os comportamentos que serão compartilhados e fazer com que apenas uma classe herde de uma classe pai e implemente n interfaces, as quais vão introduzir os comportamentos adicionais requeridos.
Se você planeja usar interfaces, mas os motivos não forem um dos citados acima, então eu lamento informar que você só está introduzindo complexidade a seu código.
Mesmo que você tenha motivos para usar interfaces, você não precisa usar interfaces para cada uma das classes que você tiver. Isso é estupidez! Como qualquer coisa em qualquer contexto, você só deve usar se for necessário em algum ponto. Um programa feito em Delphi pode conter classes simples, interfaces, e classes implementando interfaces. Tudo convivendo de forma harmoniosa.
Por fim, ainda que você tenha motivos para usar interfaces é opção sua usá-las. Praticamente tudo que se faz com interfaces pode ser feito com classes simples. Interprete este artigo apenas como um guia muito básico que tem por objetivo desmistificar as interfaces, dando a você conhecimento sobre mais uma feature que o Object Pascal disponibiliza para você sem obrigá-lo a usar.
Um exemplo simples (?)
Gostaria de propor um exemplo hipotético simples, o qual vai fazer um uso básico de interfaces. Eu pretendo, com este exemplo, atingir o maior número de pessoas, fazendo-as entender o conceito de interface, por isso entenda que o que vou escrever a seguir não tem qualquer utilidade, fora a didática. Quero aproveitar para dizer também que este exemplo não tem o intuito de convencer ninguém a usar ou deixar de usar interfaces, ele apenas servirá para mostrar de forma nua e crua a diferença entre trabalhar com interfaces e trabalhar com classes puras.
Suponha que você precise criar uma estrutura de classes que representem pessoas, levando em conta que existem dois tipos de pessoa (pessoa física e pessoa jurídica) que considere os seguintes atributos:
- nome
- razão social
- idade
- sexo
- data de nascimento
- cpf
- cnpj
Existem duas abordagens clássicas que usam apenas classes. Uma delas utiliza todos os atributos em apenas uma classe com um campo para identificar o tipo de pessoa e a outra cria classes distintas. A primeira abordagem é muito ruim, porque mistura atributos de tipos distintos de pessoas, sendo assim, vou considerar a segunda abordagem clássica, a qual se pode ver a seguir:
unit UClasses;
interface
type
TSexo = (sMasculino, sFeminino);
TCPF = String[11];
TCNPJ = String[14];
TPessoa = class
private
function GetNome: String;
procedure SetNome(const Value: String);
function GetDataNascimento: TDateTime;
procedure SetDataNascimento(const Value: TDateTime);
public
property Nome: String read GetNome write SetNome;
property DataNascimento: TDateTime read GetDataNascimento write SetDataNascimento;
end;
TPessoaFisica = class(TPessoa)
private
function getCPF: TCPF;
function GetSexo: TSexo;
procedure setCPF(const Value: TCPF);
procedure SetSexo(const Value: TSexo);
public
property Sexo: TSexo read GetSexo write SetSexo;
property CPF: TCPF read getCPF write setCPF;
end;
TPessoaJuridica = class(TPessoa)
private
function GetCNPJ: TCNPJ;
function GetNomeFantasia: String;
procedure SetCNPJ(const Value: TCNPJ);
procedure SetNomeFantasia(const Value: String);
public
property CNPJ: TCNPJ read GetCNPJ write SetCNPJ;
property NomeFantasia: String read GetNomeFantasia write SetNomeFantasia;
end;
implementation
{As implementações dos métodos foram omitidas }
var
PF: TPessoaFisica;
PJ: TPessoaJuridica;
initialization
PF := TPessoaFisica.Create;
PJ := TPessoaJuridica.Create;
PF.Nome := 'José';
PJ.Nome := 'José Maria ME';
PF.CPF := '012344321712';
PJ.CNPJ := '91221133110010';
PF.Free;
PJ.Free;
end.
Como se pode ver, a abordagem clássica é bem limpa e simples. A classe TPessoa contém atributos que eu julgo comuns tanto para pessoas físicas como pessoas jurídicas. Nome seria o nome de batismo da pessoa física, ou a razão social de uma pessoa jurídica. DataNascimento seria a data de nascimento de uma pessoa física, ou a data de fundação de uma pessoa jurídica. Simplório e bobo, eu sei, mas funciona que é uma beleza. A interpretação da estruturação de objetos usando apenas classes é a seguinte:
TPessoa é uma classe geral que possui atributos de uma pessoa qualquer, enquanto TPessoaFisica e TPessoaJuridica são classes que definem pessoas de tipos distintos, cada uma delas com atributos específicos, porém ambas herdando de TPessoa, pois ambas são Pessoas
Vejamos agora como ficaria esta estrutura caso você quisesse usar interfaces de qualquer jeito, por ser um purista de OO ou um seguidor de modinhas:
unit UInterfaces;
interface
type
TSexo = (sMasculino, sFeminino);
TCPF = String[11];
TCNPJ = String[14];
IPessoa = interface
function GetNome: String;
procedure SetNome(const Value: String);
function GetDataNascimento: TDateTime;
procedure SetDataNascimento(const Value: TDateTime);
property Nome: String read GetNome write SetNome;
property DataNascimento: TDateTime read GetDataNascimento write SetDataNascimento;
end;
IPessoaFisica = interface(IPessoa)
function getCPF: TCPF;
function GetSexo: TSexo;
procedure setCPF(const Value: TCPF);
procedure SetSexo(const Value: TSexo);
property Sexo: TSexo read GetSexo write SetSexo;
property CPF: TCPF read getCPF write setCPF;
end;
IPessoaJuridica = interface(IPessoa)
function GetCNPJ: TCNPJ;
function GetNomeFantasia: String;
procedure SetCNPJ(const Value: TCNPJ);
procedure SetNomeFantasia(const Value: String);
property CNPJ: TCNPJ read GetCNPJ write SetCNPJ;
property NomeFantasia: String read GetNomeFantasia write SetNomeFantasia;
end;
TPessoa = class(TInterfacedObject,IPessoa)
private
function GetNome: String;
procedure SetNome(const Value: String);
function GetDataNascimento: TDateTime;
procedure SetDataNascimento(const Value: TDateTime);
end;
TPessoaFisica = class(TPessoa,IPessoaFisica)
private
function getCPF: TCPF;
function GetSexo: TSexo;
procedure setCPF(const Value: TCPF);
procedure SetSexo(const Value: TSexo);
end;
TPessoaJuridica = class(TPessoa,IPessoaJuridica)
private
function GetCNPJ: TCNPJ;
function GetNomeFantasia: String;
procedure SetCNPJ(const Value: TCNPJ);
procedure SetNomeFantasia(const Value: String);
end;
implementation
{ As implementações dos métodos foram omitidas }
var
PF: IPessoaFisica;
PJ: IPessoaJuridica;
initialization
PF := TPessoaFisica.Create;
PJ := TPessoaJuridica.Create;
PF.Nome := 'José';
PJ.Nome := 'José Maria ME';
PF.CPF := '012344321712';
PJ.CNPJ := '91221133110010';
{ Não é necessário liberar da memória porque interfaces possuem contagem de
referência e são liberadas automaticamente pelo Delphi quando as variáveis
saem do escopo }
end.
O código se tornou bem maior e por isso merece algumas explicações. Para começar, foram criadas 3 interfaces e 3 classes. Cada uma das 3 interfaces define métodos set/get e propriedades correspondentes que usam estes métodos, os quais não são implementados nas interfaces. Os métodos e propriedades em cada interface são coerentes com aquela interface, isto é, eles fazem sentido apenas para aquele interface, por exemplo, a interface IPessoaFisica contém a propriedade CPF, a qual só faz sentido para uma pessoa física, já a interface IPessoaJuridica contém a propriedade NomeFantasia, a qual, do mesmo modo, só faz sentido para uma pessoa jurídica. Cada uma dessas interfaces herda da interface IPessoa, a qual possui duas propriedades (e seus métodos set/get correspondentes) que eu julguei serem gerais, por poderem ser aplicados a qualquer pessoa, seja ela física ou jurídica. A herança de interfaces é semelhante a herança de classes, só que mais simples: interfaces que herdam de uma outra interface possuem todos os métodos e propriedades somados. IPessoaFisica possui seus métodos e propriedades, mais os métodos e propriedades de IPessoa, por exemplo.
A classe TPessoa herda de TInterfacedObject[2] ao mesmo tempo em que implementa a interface IPessoa, isso significa que nós somos obrigados a implementar em TPessoa todos os métodos existentes em IPessoa, de forma a satisfazer a interface. Isso exemplifica a primeira justificativa de uso de uma interface (Obrigar a implementação de métodos). TPessoaFisica e TPessoaJuridica ambos herdam de TPessoa e cada um deles implementa sua interface específica (IPessoaFisica e IPessoaJuridica), exemplificando a segunda justificativa de uso de uma interface (Introduzir comportamentos adicionais a uma classe). Aliás, vale salientar que não precisamos redeclarar as propriedades que foram definidas nas interfaces, precisamos apenas implementar os seus métodos set/get. A interpretação da estruturação de objetos usando interfaces é a seguinte:
TPessoa é uma classe que precisa implementar alguns métodos de forma obrigatória, ao passo que TPessoaFisica e TPessoaJuridica são pessoas, pois herdam de TPessoa, porém, cada uma delas tem características especiais que as diferem uma da outra, por isso, TPessoaFisica implementa IPessoaFisica e TPessoaJuridica implementa IPessoaJuridica. Ao final a classe TPessoaFisica define uma pessoa com características físicas, enquanto TPessoaJuridica define uma pessoa com caracterísiticas jurídicas
Um exemplo a não ser seguido...
O exemplo anterior demonstra como foi introduzida mais complexidade ao código por conta do uso de interfaces. Se essa introdução de complexidade vai ou não trazer benefícios, isso já é um assunto para outras discussões que, normalmente, não levam a lugar nenhum, pois, no mundo real o nosso objetivo é satisfazer o cliente e ele não quer saber como fazemos o código, ele só quer que esse código funcione bem, e tanto o uso de interfaces como o de classes puras gera o mesmo resultado final. Gostaria então de propor um segundo problema apenas para demonstrar que interfaces não são a solução para tudo.
Suponha que você precise implementar uma estrutura de objetos para representar programadores e as linguagens com as quais ele trabalha, bem como o nível de conhecimento que um mesmo programador tem para cada uma delas. Suponha ainda que para cada linguagem o programador possua um conjunto bem específico de competências e características as quais só fazem sentido para aquela linguagem, por exemplo, programadores Delphi podem escolher trabalhar com VCL ou FMX, já programadores Java, normalmente usam um servidor web que pode ser TomCat, JBoss ou WebLogic (dentre outros). Isso deve ser observado para cada linguagem considerada, ou seja, existe uma variação muito grande de combinações de linguagens e características únicas de cada uma delas que cada programador pode utilizar ou não.
A primeira coisa que pode vir a sua mente talvez seja usar uma lista ou uma coleção na qual você poderia adicionar as linguagens e incluir, via texto, as características únicas de cada uma delas, mas convenhamos, usar texto, mesmo que esse texto seja um JSON, para atribuir características únicas a um elemento de uma coleção, não parece nem um pouco prático e é sim uma bela gambiarra.
A forma clássica de fazer isso usando simples classes seria a criação de várias classes que contenham como atributos as classes específicas de cada linguagem. Neste caso, uma classe TProgramadorDJ (Delphi e Java), por exemplo, conteria duas propriedades; uma seria LinguagemDelphi: TLinguagemDelphi e outra seria LinguagemJava: TLingagemJava. Com essa simples construção eu tenho uma classe que suporta Delphi e Java por meio de propriedades distintas. Tudo perfeito, limpo e sem gambiarras, mas como nossa intenção é ver esta implementação usando interfaces, vamos a ela:
unit UInterfaces;
interface
type
TNivelDeAprendizado = (ndaBasico, ndaIntermediario, ndaAvancado, ndaExpert);
TPlataformasSuportadas = (psDesktop, psWeb, psMobile, psDesktopWeb, psDesktopMobile, psWebMobile, psWebMobileDesktop);
TProgramador = class(TInterfacedObject)
private
FNome: String;
FIdade: Byte;
public
property Nome: String read FNome write FNome;
property Idade: Byte read FIdade write FIdade;
end;
ILinguagem = interface
function GetNivelDeAprendizado: TNivelDeAprendizado;
procedure SetNivelDeAprendizado(PNivelDeAprendizado: TNivelDeAprendizado);
property NivelDeAprendizado: TNivelDeAprendizado read GetNivelDeAprendizado write SetNivelDeAprendizado;
end;
IDelphi = interface(ILinguagem)
function GetUsaVCL: Boolean;
procedure SetUsaVCL(PValue: Boolean);
function GetUsaFMX: Boolean;
procedure SetUsaFMX(PValue: Boolean);
function GetOdeiaJava: Boolean;
procedure SetOdeiaJava(PValue: Boolean);
property UsaVCL: Boolean read GetUsaVCL write SetUsaVCL;
property UsaFMX: Boolean read GetUsaFMX write SetUsaFMX;
property OdeiaJava: Boolean read GetOdeiaJava write SetOdeiaJava;
end;
IPHP = interface(ILinguagem)
function GetUsaUmFramework: Boolean;
procedure SetUsaUmFramework(PValue: Boolean);
function GetUsaOIIS: Boolean;
procedure SetUsaOIIS(PValue: Boolean);
function GetUsaOApache: Boolean;
procedure SetUsaOApache(PValue: Boolean);
property UsaUmFramework: Boolean read GetUsaUmFramework write SetUsaUmFramework;
property UsaOIIS: Boolean read GetUsaOIIS write SetUsaOIIS;
property UsaOApache: Boolean read GetUsaOApache write SetUsaOApache;
end;
IJava = interface(ILinguagem)
function GetUsaJBoss: Boolean;
procedure SetUsaJBoss(PValue: Boolean);
function GetUsaWebLogic: Boolean;
procedure SetUsaWebLogic(PValue: Boolean);
function GetUsaTomCat: Boolean;
procedure SetUsaTomCat(PValue: Boolean);
function GetAchaQueJavaEhASolucaoPraTudo: Boolean;
procedure SetAchaQueJavaEhASolucaoPraTudo(PValue: Boolean);
property UsaJBoss: Boolean read GetUsaJBoss write SetUsaJBoss;
property UsaWebLogic: Boolean read GetUsaWebLogic write SetUsaWebLogic;
property UsaTomCat: Boolean read GetUsaTomCat write SetUsaTomCat;
property AchaQueJavaEhASolucaoPraTudo: Boolean read GetAchaQueJavaEhASolucaoPraTudo write SetAchaQueJavaEhASolucaoPraTudo;
end;
TProgramadorCompleto = class(TProgramador,IDelphi,IJava,IPHP)
private
// Campos
// IDelphi
FNivelDeDelphi: TNivelDeAprendizado;
FUsaVCL: Boolean;
FUsaFMX: Boolean;
FOdeiaJava: Boolean;
// IJava
FNivelDeJava: TNivelDeAprendizado;
FUsaJBoss: Boolean;
FUsaWebLogic: Boolean;
FUsaTomCat: Boolean;
FAchaQueJavaEhASolucaoPraTudo: Boolean;
// IPHP
FNivelDePHP: TNivelDeAprendizado;
FUsaUmFramework: Boolean;
FUsaOIIS: Boolean;
FUsaOApache: Boolean;
// Mapeamentos para desambiguação de métodos em ILinguagem
// IDelphi
function IDelphi.GetNivelDeAprendizado = GetNivelDeDelphi;
function GetNivelDeDelphi: TNivelDeAprendizado;
procedure IDelphi.SetNivelDeAprendizado = SetNivelDeDelphi;
procedure SetNivelDeDelphi(PValue: TNivelDeAprendizado);
// IJava
function IJava.GetNivelDeAprendizado = GetNivelDeJava;
function GetNivelDeJava: TNivelDeAprendizado;
procedure IJava.SetNivelDeAprendizado = SetNivelDeJava;
procedure SetNivelDeJava(PValue: TNivelDeAprendizado);
// IPHP
function IPHP.GetNivelDeAprendizado = GetNivelDePHP;
function GetNivelDePHP: TNivelDeAprendizado;
procedure IPHP.SetNivelDeAprendizado = SetNivelDePHP;
procedure SetNivelDePHP(PValue: TNivelDeAprendizado);
// Implementação dos métodos exclusivos de cada interface
// IDelphi
function GetUsaVCL: Boolean;
procedure SetUsaVCL(PValue: Boolean);
function GetUsaFMX: Boolean;
procedure SetUsaFMX(PValue: Boolean);
function GetOdeiaJava: Boolean;
procedure SetOdeiaJava(PValue: Boolean);
// IJava
function GetUsaJBoss: Boolean;
procedure SetUsaJBoss(PValue: Boolean);
function GetUsaWebLogic: Boolean;
procedure SetUsaWebLogic(PValue: Boolean);
function GetUsaTomCat: Boolean;
procedure SetUsaTomCat(PValue: Boolean);
function GetAchaQueJavaEhASolucaoPraTudo: Boolean;
procedure SetAchaQueJavaEhASolucaoPraTudo(PValue: Boolean);
// IPHP
function GetUsaUmFramework: Boolean;
procedure SetUsaUmFramework(PValue: Boolean);
function GetUsaOIIS: Boolean;
procedure SetUsaOIIS(PValue: Boolean);
function GetUsaOApache: Boolean;
procedure SetUsaOApache(PValue: Boolean);
public
// Propriedades Exclusivas do Delphi
property NivelDeDelphi: TNivelDeAprendizado read GetNivelDeDelphi write SetNivelDeDelphi;
// Propriedades Exclusivas do Java
property NivelDeJava: TNivelDeAprendizado read GetNivelDeJava write SetNivelDeJava;
// Propriedades Exclusivas do PHP
property NivelDePHP: TNivelDeAprendizado read GetNivelDePHP write SetNivelDePHP;
end;
TProgramadorWeb = class(TProgramador,IPHP,IJava)
private
// Campos
// IJava
FNivelDeJava: TNivelDeAprendizado;
FUsaJBoss: Boolean;
FUsaWebLogic: Boolean;
FUsaTomCat: Boolean;
FAchaQueJavaEhASolucaoPraTudo: Boolean;
// IPHP
FNivelDePHP: TNivelDeAprendizado;
FUsaUmFramework: Boolean;
FUsaOIIS: Boolean;
FUsaOApache: Boolean;
// Mapeamentos para desambiguação de métodos em ILinguagem
// IJava
function IJava.GetNivelDeAprendizado = GetNivelDeJava;
function GetNivelDeJava: TNivelDeAprendizado;
procedure IJava.SetNivelDeAprendizado = SetNivelDeJava;
procedure SetNivelDeJava(PValue: TNivelDeAprendizado);
// IPHP
function IPHP.GetNivelDeAprendizado = GetNivelDePHP;
function GetNivelDePHP: TNivelDeAprendizado;
procedure IPHP.SetNivelDeAprendizado = SetNivelDePHP;
procedure SetNivelDePHP(PValue: TNivelDeAprendizado);
// Implementação dos métodos exclusivos de cada interface
// IJava
function GetUsaJBoss: Boolean;
procedure SetUsaJBoss(PValue: Boolean);
function GetUsaWebLogic: Boolean;
procedure SetUsaWebLogic(PValue: Boolean);
function GetUsaTomCat: Boolean;
procedure SetUsaTomCat(PValue: Boolean);
function GetAchaQueJavaEhASolucaoPraTudo: Boolean;
procedure SetAchaQueJavaEhASolucaoPraTudo(PValue: Boolean);
// IPHP
function GetUsaUmFramework: Boolean;
procedure SetUsaUmFramework(PValue: Boolean);
function GetUsaOIIS: Boolean;
procedure SetUsaOIIS(PValue: Boolean);
function GetUsaOApache: Boolean;
procedure SetUsaOApache(PValue: Boolean);
public
// Propriedades Exclusivas do Java
property NivelDeJava: TNivelDeAprendizado read GetNivelDeJava write SetNivelDeJava;
// Propriedades Exclusivas do PHP
property NivelDePHP: TNivelDeAprendizado read GetNivelDePHP write SetNivelDePHP;
end;
implementation
{ A implementação de todos os métodos foi omitida }
var
Carlos: TProgramadorCompleto;
WebSon: TProgramadorWeb;
initialization
Carlos := TProgramadorCompleto.Create;
// Propriedades de TProgramador
Carlos.Nome := 'Carlos Barreto Feitoza Filho';
Carlos.Idade := 38;
// Propriedades em ILinguagem em cada interface específica
Carlos.NivelDeDelphi := ndaExpert;
Carlos.NivelDePHP := ndaIntermediario;
Carlos.NivelDeJava := ndaBasico;
// Propriedades em IDelphi
(Carlos as IDelphi).UsaVCL := True;
(Carlos as IDelphi).UsaFMX := False;
(Carlos as IDelphi).OdeiaJava := True;
// Propriedades em IJava
(Carlos as IJava).UsaJBoss := True;
(Carlos as IJava).UsaWebLogic := True;
(Carlos as IJava).UsaTomCat := False;
(Carlos as IJava).AchaQueJavaEhASolucaoPraTudo := False; // definitivamente NÃO!
// Propriedades em IPHP
(Carlos as IPHP).UsaUmFramework := False;
(Carlos as IPHP).UsaOApache := True;
(Carlos as IPHP).UsaOIIS := False;
WebSon := TProgramadorWeb.Create;
// Propriedades de TProgramador
WebSon.Nome := 'WebSon Heverton Taurus Tiobe Portaluppi';
WebSon.Idade := 38;
// Propriedades em ILinguagem em cada interface específica
WebSon.NivelDePHP := ndaIntermediario;
WebSon.NivelDeJava := ndaBasico;
// Propriedades em IJava
(WebSon as IJava).UsaJBoss := True;
(WebSon as IJava).UsaWebLogic := True;
(WebSon as IJava).UsaTomCat := True;
(WebSon as IJava).AchaQueJavaEhASolucaoPraTudo := False;
// Propriedades em IPHP
(WebSon as IPHP).UsaUmFramework := True;
(WebSon as IPHP).UsaOApache := True;
(WebSon as IPHP).UsaOIIS := True;
{ Como as classes TProgramador* herdam de TProgramador, que herda de
TInterfacedObject, seus objetos são automaticamente liberado da memória quando
suas variáveis saem do escopo }
end.
Não se assuste meu caro, trabalhar com interfaces pode gerar mais código mesmo quando você não sabe o que está fazendo. Vou tentar explicar isso tudo. As linhas 9 a 16 definem uma a classe básica de programador, pois todo programador tem um nome e uma idade. As linhas 18 a 23 definem a interface básica de uma linguagem, pois para cada linguagem um programador possui um nível distinto de aprendizado. As linhas 25 a 65 definem as interfaces específicas de cada linguagem. Nelas podem ser definidos métodos e propriedades que fazem sentido apenas em cada linguagem. As linhas 67 a 135 definem a classe de um programador completo, o qual trabalha com todas as linguagens disponíveis. A classe TProgramadorCompleto herda de TProgramador (que contém nome e idade) e implementa cada uma das interfaces de linguagens de programação disponíveis. As linhas 137 a 186 definem uma classe que representa um programador web, o qual trabalha apenas com Java e PHP. A classe TProgramadorWeb herda de TProgramador e implementa apenas as interfaces IJava e IPHP, que são as linguagens que um programador web (neste exemplo) pode usar.
Quero ressaltar algo que muitos de vocês não conhecem, chama-se cláusula de resolução de método. Ao escrever este exemplo eu criei uma interface básica ILinguagem, a qual possui uma propriedade (NivelDeAprendizado) e seus métodos set/get. As interfaces IDelphi, IJava e IPHP herdam de ILinguagem e, como sabemos, a herança de interfaces soma todos os métodos existentes tanto na interface atual como na interface "pai". Como, por exemplo, TProgramadorWeb implementa duas interfaces que tem um pai comum, um problema foi introduzido: como implementar um método que existe em duas interfaces distintas? A solução para isso é usar a cláusula de resolução de método, a qual dá um "apelido" a métodos que vem de interfaces distintas. As linhas 154, 156, 159 e 161 de TProgramadorWeb, por exemplo, fazem uso de cláusulas de resolução de método para diferenciar os métodos de mesmo nome existentes em interfaces distintas. Abaixo estão estas declarações:
function IJava.GetNivelDeAprendizado = GetNivelDeJava;
procedure IJava.SetNivelDeAprendizado = SetNivelDeJava;
function IPHP.GetNivelDeAprendizado = GetNivelDePHP;
procedure IPHP.SetNivelDeAprendizado = SetNivelDePHP;
A interpretação destas declarações é muito simples, por exemplo, a primeira linha (function IJava.GetNivelDeAprendizado = GetNivelDeJava) diz que a função GetNivelDeAprendizado, da interface IJava, será conhecida localmente (em TProgramadorWeb), por GetNivelDeJava. Após definir o apelido para cada método ambíguo, basta implementá-los, tal como foi feito nas linhas 155, 157, 160 e 162. Isso resolve o problema da ambiguidade!
As linhas 192 a 194 declaram duas variáveis dos dois tipos que definimos. As linhas 197 a 217 são um exemplo de como usar a classe TProgramadorCompleto, note que a criação do objeto é trivial (linha 197), bem como a atribuição das duas propriedades herdadas de TProgramador (Nome e Idade, nas linhas 199 e 200). As linhas 202 a 204 são propriedades ligadas aos apelidos definidos por meio das cláusulas de resolução de método. Ao atribuirmos um valor a cada uma destas propriedades, na verdade estamos atribuindo estes valores à propriedade NivelDeAprendizado de cada uma das interfaces que nossa classe implementa.
A coisa começa a ficar estranha a partir de agora. As linhas 206 a 217, são atribuições às propriedades específicas de cada interface. Mas porque é preciso fazer toda essa manobra para acessar as propriedades de cada interface? Primeiramente, o operador as do Delphi, basicamente realiza um typecast. Ao dizer, por exemplo, Carlos as IDelphi, estamos fazendo um typecast que permite acessar Carlos como se ele fosse IDelphi e já que a classe de Carlos (TProgramadorCompleto) implementa IDelphi, o typecast será bem sucedido. Uma classe que implementa várias interfaces, pode assumir a personalidade de cada uma destas interfaces usando-se o operador as do Delphi.
Como a variável Carlos é do tipo TProgramadorCompleto, podemos acessar diretamente apenas métodos e propriedades definidas em TProgramadorCompleto ou, no caso, TProgramador, que é sua classe pai. É por isso que conseguimos acessar as propriedades Nome, Idade, NivelDeDelphi, NivelDeJava e NivelDePHP. Nós não precisaríamos realizar o typecast, caso usássemos diretamente os métodos Set/Get, os quais nós implementamos em TProgramadorCompleto, mas eu gosto de manter os métodos Set/Get privados, logo, a única forma de manipular as propriedades de interfaces específicas é por meio do typecast usando o operador as do Delphi. De forma resumida, para acessar uma interface implementada por uma classe, precisamos acessar a instância do objeto desta classe como se ela fosse a interface usando o operador as. "AS" vem do inglês e significa "COMO", logo, dizer "Classe as Interface" pode ser traduzido como "Classe como Interface" ou, para ficar mais claro, "acessar Classe como se ela fosse a Interface".
Como se pode ver o uso de interfaces aglutina numa mesma classe os métodos e propriedades de várias interfaces. Não precisam existir subclasses numa classe que implementa interfaces no entanto, existe um conceito de delegação no qual parece que estamos usando subclasses, mas a implementação de interfaces por delegação é mais complexa do que usar uma simples subclasse, logo eu não a estou considerando como vantagem aqui.
Conclusão
O Delphi é uma linguagem evoluída e que possui inúmeros conceitos avançados, incluindo as interfaces e classes com métodos virtuais abstratos. Algumas outras linguagens não possuem o conceito de método virtual abstrato e talvez por isso, nestas linguagens o uso de interfaces para implementar certos tipos de programação específica seja imprescindível. No Delphi, por outro lado (com algumas exceções) o uso de interfaces pode ser dispensado para garantir um código mais limpo e fácil de se entender.
Se você costuma usar interfaces no seu código e está feliz com isso, tudo bem, mas se você nunca usou interfaces e pretende usar, meu conselho é que você busque outras fontes além deste artigo a fim de decidir se elas vão trazer vantagens reais, se você está apenas tentando seguir algum tipo de modinha ou se está tentando usar apenas por causa de alguma promessa futura (muito futura!) de escalabiliade ou manutenibilidade as quais eu, particularmente, acredito que não justificam o uso de interfaces no Delphi.
Com toda sinceridade, até hoje eu só vi utilidade nas interfaces utilizando o Delphi quando eu precisei obrigar a implementação de certos métodos em componentes de conexão (conectores) do TUserControl, os quais possuem métodos comuns que precisam ser implementados de forma diferente para cada engine de conexão considerada, mas precisam, ao mesmo tempo, ser ligados a um componente principal por meio de uma propriedade única. Neste mesmo uso que eu faço eu aplico também a segunda justificativa, a "injeção" de métodos em uma classe sem alterar sua hierarquia. Tirando isso, todas as vezes que penso em usar interfaces eu termino usando simples classes e tudo fica extremamente limpo e simples, do jeito que eu gosto, do jeito que TODOS deveríamos gostar.