Parte 6

<< Click to Display Table of Contents >>

Navigation:  Novatos > Orientação a objetos >

Parte 6

Previous pageReturn to chapter overviewNext page

Descrição

 

O objetivo deste artigo é continuar uma série de publicações sobre a Orientação a Objetos, tratando de outros tipos relacionados com a OO. Não pretende aprofundar-se nos temas propostos, mas apenas dar uma visão superficial do assunto aos leitores que não o conhecem.

 

Pré-requisitos

 

Habilidade em programação com delphi em qualquer versão, conhecimento de Object Pascal, noções de Orientações a Objeto.

 

Introdução

 

Nos cinco primeiro artigos pudemos fazer uma abordagem geral sobre a OO. Fizemos uma seção introdutória no primeiro artigo, aprofundando-a nos outros três seguintes, cada um falando de um dos três principais conceitos da OO, e por fim tratamos da interação entre os objetos. Pois neste artigo iremos tratar de outros complementares, mas igualmente importantes relativos a este assunto. Usaremos do corpo deste artigo para tratar dois assuntos que não estão necessariamente ligados entre si.

 

Primeiro vamos falar de meta-classes, que é um assunto um pouco mais complexo, e requererá uma certa abstração por parte do leitor. Em segundo lugar vamos falar de interfaces, que são uma última parte fundamental a respeito de objetos, estas que ocupam uma posição privilegiada nos bons projetos OO.

 

Meta-classes

 

Nesta seção vamos tratar de um conceito um pouco mais abstrato, que não podíamos tratar nos primeiros artigos. Não podíamos porque senão o leitor poderia ficar bastante confuso, pois convenhamos que a própria OO já não é tão simples assim de entender.  Meta-classes, pra quem já não raciocinar “em OO”, pode nem descer pela garganta.

 

Resumidamente, meta-classes são classes de classes, ou seja, classes que referenciam classes. São declarações de tipos que referenciam classes como cada um dos valores que se pode assumir. Entendeu? Ainda não? Já era de se imaginar.

 

Vamos aos poucos, com exemplos. Vejamos a declaração abaixo:

 

type

  TMovel = class

  end;

 

  TMesa = class(TMovel)

  end;

 

  TCadeira = class(TMovel)

  end;

 

var

  MeuObjeto: TMesa;

 

 

 

Sabemos que MeuObjeto possui uma instância, ou valor, cujo tipo é TMesa. Agora vamos abstrair um pouco mais e imaginar que TMesa é uma instância. Instância da classe das classes. Se tivéssemos a seguinte declaração:

 

var

  ClasseDeMeuObjeto: TClass;

 

Poderíamos atribuir a esta variável o valor TMesa – uma referência para a classe, e não uma instância dela. Loucura?

 

Acontece que existe na unit System.pas a seguinte declaração:

 

type

  TClass = class of TObject;

 

Que quer dizer que TClass é um novo tipo, e representará as classes. Isso seria algo parecido com:

 

type

  TClass = (TObject, TMesa, TCadeira ... );

 

 

Para quem já conhece os tipos enumerados no Delphi, a coisa ficou mais clara. O que acontece na prática é que uma variável de meta-classe, tal como ClasseDeMeuObjeto, declarada acima, assume um valor que aponta para a classe do objeto. Assim, toda variável do tipo TClass poderá assumir qualquer classe derivada de TObject, como por exemplo:

 

procedure TForm1.AtribuirMetaclasse;

begin

  ClasseDeMeuObjeto := TMesa;

end;

 

 

Com isto teremos, em ClasseDeMeuObjeto, um valor variável de referência de classe, que no momento, está assumindo TMesa. Duvida? Então vamos enlouquecer ainda mais. Vamos fazer a seguinte instanciação:

 

procedure TForm1.InstanciarAClasseReferida;

begin

  MeuObjeto := ClasseDoMeuObjeto.Create;

end;

 

Ora, sabe-se que ClasseDoMeuObjeto não é uma classe. Mas ela atualmente aponta para uma. E isto fará, pelo fato desta variável estar assumindo o valor TMesa no momento, MeuObjeto receber uma instância de TMesa.

 

Uma boa maneira de ter certeza disso é usar os métodos ClassName e ClassType, presentes em TObject. Estes métodos dizem respeito à classe atual do objeto. O primeiro é uma meta-classe que retorna a classe concreta do objeto. O segundo, retorna o nome desta classe. Então:

 

procedure TForm1.InstanciarAClasseReferida;

begin

  MeuObjeto := ClasseDoMeuObjeto.Create;

  ShowMessage(MeuObjeto.ClassName);

end;

 

Esta chamada deverá provar de uma vez por todas que foi instanciado um objeto do tipo TMesa, pois o diálogo exibirá o nome da classe instanciada.

 

Muito embora a meta-classe padrão e mais genérica seja TClass, o leitor poderá limitar o escopo de valores que suas variáveis de meta-classes irão assumir, declarando-as a partir de uma outra classe que não seja TObject:

 

type

  TMinhaMetaClasse = class of TMinhaClasse;

 

Esta declaração irá limitar as variáveis do tipo TMinhaMetaClasse a apenas TMinhaClasse e suas descendentes. Consulte os métodos existentes em TObject, de preferência olhando a ajuda do Delphi. Muitas coisas interessantes sobre classes e meta-classes estão presentes ali.

 

Interfaces

 

Em artigos anteriores, chegamos a tratar de superficialmente herança múltipla. Pois refrescando a memória de nossos leitores, herança múltipla é a capacidade de uma classe descender de mais de uma outra classe ao mesmo tempo, e faz a classe herdeira adquirir tanto a vantagem de ser abstraída pelos dois tipos, quanto a de herdar todas suas características simultaneamente.

 

Também dissemos, anteriormente, que o Delphi não implementa a herança múltipla. Nem o Delphi, e nem outras linguagens modernas, tais como Java. Isto porque este recurso não é algo tão simples, nem para quem o usa (o programador), e nem para quem o faz (o projetista da linguagem).

 

Uma solução alternativa à herança múltipla, adotada pelo Delphi, chama-se interface. A interface vem com o intuito de suprir muitos dos benefícios da herança múltipla, simplificando os seus objetos relacionados à abstração das classes. Ela também tem um papel fundamental no desacoplamento, algo que veremos mais à frente.

 

Uma interface é similar a uma classe abstrata, mas com duas grandes diferenças. A primeira, como já sugerimos, é a questão da multiplicidade da “herança” (que, para o caso de interfaces, chama-se implementação, ou realização). Uma classe que implemente n interfaces poderá ser referenciada por quaisquer variáveis do tipo de cada uma destas n interfaces.

 

A segunda diferença é que uma interface não possui atributos e nem implementação de métodos, e por isso nunca se poderá criar uma instância de uma interface. Uma interface carrega consigo apenas declarações de métodos, e servirá para “abstrair” outras classes concretas, permitindo que os elementos destas classes serem tratados como do tipo da interface.

 

Bom, vamos à prática. A declaração de uma interface no Delphi difere de uma classe no fato de não permitir declaração de atributos. Interfaces também não suportam regiões de escopo de visibilidade (public, private, protected). Sugere que inicie-se os nomes de interfaces com a letra “I”, assim como iniciamos nossas classes com “T”. Eis o nosso exemplo:

 

type

  IMinhaInterface = interface

    procedure MeuMetodo1;

    function MeuMetodo2: TObject;

    procedure MeuMetodo3(const X: TMeuObjeto);

    procedure MeuMetodo4(Y: IMinhaInteface);

    function MeuMetodo5: Integer;

  end;

 

  IMinhaOutraInterface = interface

    procedure MeuMetodo6(const X: Integer);

    procedure MeuMetodo7(const X: string);

  end;

 

E a nossa classe que irá realizar aquela interface ficará desta maneira:

 

type

  TMinhaClasse = class(TMinhaAncestral, IMinhaInterface, IMinhaOutraInterface)

    procedure MeuMetodo1;

    function MeuMetodo2: TObject;

    procedure MeuMetodo3(const X: TMeuObjeto);

    procedure MeuMetodo4(Y: IMinhaInteface);

    function MeuMetodo5: Integer;

    procedure MeuMetodo6(const X: Integer);

    procedure MeuMetodo7(const X: string);

  end;

 

Note que todos os métodos declarados nas interfaces devem ser implementados pela classe que as realize. Se algum método for esquecido, o compilador não permitirá a compilação. Uma vez feito estas declarações, TMinhaClasse passa a ser reconhecida como do tipo TMinhaAncestral, IMinhaInterface e IMinhaOutraInterface, todos ao mesmo tempo. Isto pode ser verificado com a seguinte atribuição, que é naturalmente aceita pelo compilador:

 

procedure TForm1.AtribuirAInterface;

var 

  X: IMinhaInterface;

begin

  X := TMinhaClasse.Create;

end;

 

E de maneira semelhante às classes ancestrais e descendentes, a atribuição é permitida, onde um objeto da classe TMinhaClasse é atribuído a uma referência do tipo IMinhaInterface. Da mesma maneira, o objeto apontado por X será tratado como IMinhaInterface.

 

Mas se uma interface não suporta declaração de atributos e nem a implementação dos métodos, então pra que diabos ela serve? A princípio, digamos que uma interface é uma forma de dar um novo tipo abstrato à sua classe, sem limita-la pela herança. Os métodos da interface serão uma forma de obrigar que sejam implementados nas classes que assumem aquela interface, para que de fato ela a realize. Vejamos um novo exemplo:

 

type

  IPersistente = interface

    procedure Salvar;

    procedure Abrir(const Nome: string);

    procedure Excluir;

    procedure SalvarComo(const Nome: string);

  end;

 

  IClonavel = interface

    function Clonar: TObject;

  end;

 

  IVisivel = interface

    procedure Mostrar;

    procedure Esconder;

  end;

 

Primeiramente declaramos três interfaces independentes, IPersistente, IClonavel e IVisivel. O objetivo delas é criar moldes para que outras classes – independentemente de quem elas herdem – possam assumir este tipo. Façamos isso:

 

type

  TArquivo = class(TObjetoDoSistema, IPersistente, IClonavel)

  public

    procedure Salvar;

    procedure Abrir(const Nome: string);

    procedure Excluir;

    procedure SalvarComo(const Nome: string);

    function Clonar: TObject;

  end;

 

  TCaixaDeTexto = class(TEdit, IVisivel, IClonavel)

  public

    procedure Mostrar;

    procedure Esconder;

    function Clonar: TObject;

  end;

 

Deixaremos implícito que cada uma das classes implementarão seus métodos de forma uma diferente, mas sempre respeitando ao que o nome do método propõe. O que acontece agora é que as classes TArquivo e TCaixaDeTexto, podem serem qualificadas como IClonavel, como se IClonavel fosse uma classe, e fossem herdeiras dela. O mais fantástico disso é que as duas classes são descendentes de classes concretas diferentes, como o leitor pode perceber. Isto permite que qualquer classe concreta, que implemente IClonavel, possa ser passada como parâmetro ao método abaixo:

 

procedure TClasseX.Replica(const c: IClonavel; Copias: array of TObject;

  const NoCopias: Integer);

var

  i: Integer;

begin

  for i := Length(Copias) to Length(Copias) + NoCopias do

  begin

    SetLength(Copias, i);

    Copias[i] := c.Clonar;

  end;

end;

 

Observemos que este método aceita como parâmetro qualquer classe que implemente IClonavel, e funcionará independentemente de que classe concreta seja, pela chamada ao seu método Clonar. Isto tem algo de familiar, não? Polimorfismo. As interfaces também suportam o polimorfismo, porém dando uma maior flexibilidade do que classes abstratas, por não limitar as classes pela herança.

 

Uma observação interessante é que as interfaces – uma vez que nunca assumem formas concretas – estão para os adjetivos tanto quanto as classes estão para os substantivos. Esta analogia costuma ser aplicada, pois as interfaces tendem a serem nomeadas por adjetivos, enquanto as classes por substantivos.

 

Interfaces como uma boa prática

 

Conforme sugerimos mais acima, as interfaces são uma boa maneira de se produzir bons projetos, porque promovem o desacoplamento de classes. Isto quer dizer que podemos referenciar um objeto pela sua interface, desprendendo assim da necessidade de conhecer a classe concreta a que eles pertencem.

 

Isto tem uma razão além do que a própria abstração do conhecimento da classe do objeto: a manutenibilidade de código. Grandes autores costumam sugerir que todos – ou pelo menos uma maioria significativa dos objetos do sistema – sejam construídos sempre com uma implementação (classe) e uma abstração (interface). O motivo disso é permitir que a implementação possa ser alterada sem que haja alteração de código das partes dependentes, apenas fazendo a nova classe substituta realizar a interface em questão.

 

Boas práticas de OO, envolvendo principalmente desacoplamento de classes serão vistas no próximo artigo, onde iremos abordar especificamente este tema. Por enquanto, nos limitaremos ao conceito e a uma primeira compreensão das interfaces.

 

Considerações Finais

 

Este artigo visou apenas tratar destes dois aspectos da OO que não couberam nos conceitos abordados dos artigos anteriores, mas que são igualmente importantes. Meta-classes são um conceito um pouco mais complexo envolvendo as classes, mas que é relativamente usado no Delphi. Por isso não pudemos deixar de abordá-lo.

 

As interfaces, por sua vez, são importantíssimas para qualquer bom projeto OO. Veremos mais à que elas promovem o desacoplamento, o que é fundamental para a criação de boas soluções de objetos quanto à manutenção do código. Isto se torna bastante claro quando se há uma mudança nos requisitos do sistema. As interfaces permitem que seus objetos sejam referenciados, sem obrigar o conhecimento da classe concreta a que eles pertencem, nem tampouco interferir na sua herança.

 

Numa próxima publicação – que pretendemos que seja a última desta série – trataremos de boas práticas de OO, aplicando todos os conceitos que vimos, no intuito de que nossos projetos sejam melhorados, tanto em termos de funcionalidades quanto de manutenibilidade de código.