Desmistificando as Interfaces no Delphi

Categoria: Artigos
Categoria Pai: Addicted 2 Delphi!
Acessos: 32626
Imagem meramente ilustrativa

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:

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:

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.



1 Esta é uma interpretação pessoal, sem rodeios e que sintetiza o conceito de interface ao máximo, de forma que um leigo possa entender. Não cito, propositalmente, qualquer definição técnica ou justificativa embasada em "programação teórica". Aqui tudo é real!
2 Cada interface básica herda de IInterface a qual possui alguns métodos que precisam ser implementadas obrigatoriamente. Quando uma classe implementa uma interface qualquer, ela precisa implementar todos os métodos desta interface, incluindo todos os métodos das interfaces das quais esta interface herda e isso inclui os métodos existentes em IInterface. TInterfacedObject é uma classe que já implementa os métodos de IInterface, de forma que você não precise se preocupar em implementá-los, já que eles não fazem parte da sua programação. IInterface está para as interfaces assim como TObject está para as classes