Desmistificando as Interfaces no Delphi - Um exemplo a não ser seguido...
Escrito por Carlos B. Feitoza Filho | |
Categoria: Artigos | |
Categoria Pai: Addicted 2 Delphi! | |
Acessos: 34372 |
Páginas dentro deste artigo
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.