[Pesquisar este blog]

segunda-feira, 26 de março de 2018

POO::Plena-23-Polimorfismo

POO-P-22-Herança MúltiplaPOO-A-24-Polimorfismo, upcasting e downcasting
A palavra polimorfismo vem do grego poli morfos e significa muitas formas. É a característica chave da orientação a objetos que torna possível utilizar múltiplas formas de construção de objetos. Na OO o polimorfismo de manifesta na sobrecarga, na herança, na sobreposição e também na generalização.

Na verdade, o polimorfismo é um mecanismo único, semelhante a uma força. Em uma direção ele provê a especialização (de tipos), enquanto no sentido oposto oferece a generalização (de tipos). É por meio do polimorfismo que as linguagens OO proporcionam mecanismos de classificação e também de reuso.

Mecanismos do polimorfismo::sobrecarga

A sobrecarga de métodos (method overload) é uma propriedade da OO que permite a existência de dois ou mais métodos com o mesmo nome em uma mesma classe, desde que possuam assinaturas diferentes. Devemos recordar que a assinatura de um método é a combinação de seu nome com a lista dos tipos de seus parâmetros formais. 

Assim, o método estático pow da classe Math, que toma dois parâmetros do tipo double, tem assinatura pow(double, double); o método getClass() da classe Objeto não toma parâmetros, sendo sua assinatura getClass(void); enquanto o método get da classe ArrayList toma um inteiro como argumento e tem assinatura get(int).

É a assinatura diferente que possibilita a diferenciação do método que deve ser acionado no ponto de sua invocação (i.e., na chamada do método); por isso o tipo de retorno é desconsiderado e não faz parte da assinatura, como ilustra a figura que segue.


Desta maneira, métodos conceitualmente semelhantes podem possuir o mesmo nome, diferenciando-se pela lista requerida de argumentos, que possibilita que cada um realize sua operação de maneiras distintas. Por exemplo, a classe String oferece quatro métodos com nome indexOf:
  • indexOf(char) retorna a posição onde se localiza a primeira ocorrência do caractere fornecido como argumento;
  • indexOf(char, int) retorna a posição da primeira ocorrência do caractere suprido como argumento, a partir da posição indicada;
  • indexOf(String) retorna a posição onde se encontra a primeira ocorrência da substring indicada como argumento;
  • indexOf(String, int) retorna a posição da primeira ocorrência da substring dada como argumento, a partir da posição indicada.

A existência de métodos sobrecarregados proporciona flexibilidade para o uso da classe, pois várias formas das operações estão presentes, ao mesmo tempo que mantém a interface da classe simples, porque conceitualmente os métodos sobrecarregados fornecem uma mesma operação.

Construtores também podem ser sobrecarregados, aumentando as possibilidades de criação de objetos. A classe String possui 11 diferentes construtores, que permitem várias formas de instanciação de cadeias de caracteres.

Mecanismos do polimorfismo::herança

A herança (inheritance) é, talvez, a segunda característica mais importante da OO. É o mecanismo que permite a construção de novos tipos de dados (classes) baseada em outros tipos já existentes, possibilitando a criação de hierarquias de classes, como ilustrado a seguir.

Tipicamente, uma hierarquia de classes é como uma árvore cuja raiz é colocada na parte superior do diagrama. A classe-raiz, que dá origem às demais, é colocada no topo, assim, de modo que, para qualquer classe presente na árvore, seus tipos ascendentes ficam acima, enquanto seus descendentes ficam abaixo.

Na herança, uma classe, tomada como superclasse ou classe-pai, pode dar origem a uma ou mais novas classes denominadas de subclasses ou classes-filha. Esta relação pai-filha, identificada pela resposta afirmativa a questão "é um? | é do tipo?", conecta as classes no diagrama e permite, do ponto de vista de implementação, que:
  • as características ancestrais sejam compartilhadas pelos descendentes;
  • novas características possam ser adicionadas nos descendentes;
  • características presentes nos ascendentes sejam modificadas ou restringidas.

O compartilhamento da implementação da superclasse nas subclasse já é um ganho, pois atributos e operações públicas e protegidas tornam-se acessíveis para todos os descendentes da classe, evitando repetição de código e facilitando a manutenção. Isto é um mecanismo efetivo de simplificação de projeto e de reuso.

Além disso, ao adicionar novos atributos e operações, a subclasse é diferenciada de seu ancestral, o que corresponde a um mecanismo de extensão ou de especialização das classes.

Também é possível modificar o comportamento de operações existentes, utilizando outro mecanismo decorrente do polimorfismo denominado sobreposição, que será comentado um pouco mais a frente.

A adição de novos atributos e novas operações é a forma mais comum de aproveitar o mecanismo da herança. Nesta situação procura-se identificar os elementos comuns a um conjunto de classes, mantendo tais  elementos comuns em uma classe que funcionará como ponto de partida comum para um conjunto de outras classes, tal como na ilustração acima, onde a classe Funcionario concentra aspectos comuns de todos os tipos de funcionário de uma empresa, possibilitando que novas classes, especializadas em funções específicas, como Caixa, Motorista, Segurança ou Gerente, possam ser criadas.

Para auxiliar no tarefa de fatoração das características comuns e também na identificação daquelas que são específicas, a organização de uma tabela de classes/tipos e suas funcionalidades pode ser muito útil. Partindo da narrativa descritiva do problema, as classes que representam as entidades/atores do problema usualmente figuram como substantivos. Outros substantivos, adjetivos e complementos costumam indicar os atributos destas entidades; já os verbos apontam as funcionalidades que serão representadas por métodos das classes associadas.

É claro que o benefício da herança é permitir o compartilhamento da implementação dos aspectos comuns destes funcionários, sem necessidade de repetição deste código. Isto mantém a coesão necessária a cada classe, ao mesmo tempo que promove o reuso, facilita a manutenção do código e ainda permite que, no futuro, novas classes sejam adicionadas à hierarquia construída.

Mecanismos do polimorfismo::sobreposição

A sobreposição (ou substituição) de métodos, também conhecida como method overriding, consiste na implementação de um método na subclasse com a mesma assinatura de outro existente na superclasse.

Isto permite dotar a subclasse de uma implementação distinta daquela existente na superclasse, mas mantendo sua interface, o que facilita seu uso por meio do polimorfismo, como será visto mais à frente.

É necessário frisar que a sobreposição envolve a criação de um novo método, com a mesma assinatura, de uma operação existente nas superclasses daquela onde é codificado. Assim, isto constitui um mecanismo distinto da sobrecarga, que envolve a criação de métodos com mesmo nome, mas assinaturas diferentes, em uma mesma classe.

Em conjunto com a herança, a sobreposição permite que uma subclasse tenha comportamento diferente da superclasse, mantendo sua interface. Esta é uma outra forma de especialização da classe, sem ampliação de suas características.

Além disso, métodos considerados inadequados na subclasse, embora não possam ser ocultos (ou seja, deixarem de ser públicos), suas versões sobrepostas podem lançar exceções impedindo seu uso. Com isso, a sobreposição permite a contração de classes, ou seja, a redução de suas funcionalidades.

Mecanismo da sobrecarga::generalização

Sempre é adequado destacar que o polimorfismo é a característica mais importante da OO, pois representa o mecanismo pelo qual vários tipos de objetos, ou seja, várias formas de implementação, podem ser tratadas como sendo de um tipo comum.

Por isso é importante observar que:
  • Uma referência (de instância) é uma variável cujo tipo é uma classe.
  • Cada referência aponta para UM objeto de cada vez.
  • É possível trocar o valor de uma referência, por outra, para que ela aponte para OUTRO objeto.
  • O valor null indica que a referência não aponta qualquer objeto válido.
  • Várias referências diferentes podem apontar para um mesmo objeto.

No Java, em particular, as referências não são endereços de memória, mas identificadores de objetos definidos pela máquina virtual Java (JVM). Por meio desses identificadores, a JVM verifica quando os objetos estão sendo referenciados (em uso aparente). Se a JVM detecta que um objeto deixa de ser referenciado, isto é, quando não existem referências apontando para um objeto específico, ele é marcado pelo Automatic Garbage Collector para descarte (remoção da memória).

Também sabemos que na plataforma Java, qualquer classe é, direta ou indiretamente, uma subclasse de java.lang.Object, pois mesmo numa declaração de classe simples, implicitamente usamos o mecanismo da herança, tomando como superclasse o tipo Object, que constitui assim a raiz da hierarquia de classes Java. Com o C# ocorre o mesmo, mas com a classe System.Object sendo a raiz da hierarquia de classes.

Sendo assim, considerando qualquer hierarquia de classes, toda classe existente é uma subclasse de Object, de modo que uma variável do tipo Object pode armazenar referências de qualquer tipo de objeto:

Object o;
o = new String("Polimorfismo");
o = new Integer(123456);
o = new ArrayList();
o = new FileReader("arquivoTexto.txt");
o = new JFrame("GUI");
o = new Thread("fluxo de processamento");

O polimorfismo permite que um objeto seja, transparentemente, tratado como qualquer outro tipo ascendente (superclasses da qual é derivado), e também como sendo do seu tipo verdadeiro, isto é, como foi instanciado de fato.

A possibilidade de admitir múltiplos tratamentos para um mesmo objeto é a própria definição de polimorfismo. Mas existe uma condição envolvida nesta forma do polimorfismo: um objeto pode tratado como do seu próprio tipo, como sendo de qualquer um de seus ancestrais, até a raiz da hierarquia, ou seja, como do tipo Object.

Se observarmos novamente a figura da hierarquia de funcionários, podemos concluir que as classes Caixa, Motorista, Segurança e Gerente, derivadas diretamente da classe Funcionario, podem ter seus objetos tratados como sendo do tipo Funcionario, ou mesmo Object.

Isso possibilita a generalização, ou seja, uma operação que faz o contrário da herança, que permite a especialização.

Um objeto cuja classe é derivada de outra é como uma composição de objetos em camadas. A camada mais externa corresponde ao tipo real do objeto (por exemplo Gerente) e nela se encontram as adições providas por esta classe (sua especialização em relação à camada anterior). A camada mais interna é sempre aquela que corresponde ao tipo Object, onde estão presentes seus elementos. As camadas intermediárias são aquelas providas pelos descendentes de Object até a camada mais externa.

A figura que segue ilustra um objeto de uma possível classe Comissionado, a qual é derivada de (uma subclasse de) Funcionario, que por sua vez é derivada de (uma subclasse de) Object. Assim, o objeto possui três camadas, uma provida por cada classe de sua hierarquia, a mais interna correspondente a Object e a mais externa correspondente ao seu tipo real (ao tipo de sua criação -- instanciação).


Conforme o tipo da referência usada para acessar um objeto, determina-se quais camadas serão efetivamente acessadas. Se é utilizada uma referência do tipo real (de instanciação), temos acesso a todas os membros públicos deste tipo e de todos os tipos ancestrais (ascendentes). Na figura acima, a referência do tipo Comissionado pode acessar todos os membros públicos do objeto presentes na camada mais externa (Comissionado), na camada intermediária (Funcionario) e também na camada mais interna (Object).

Mas se a referência é do tipo Funcionario, só é possível acessar os membros desta camada (Funcionario) e das mais internas (no caso apenas Object). Uma referência de tipo Funcionário não consegue acessar nada presente em camadas mais externas do que seu tipo, porque sua classe não prevê sua existência, pois foram adicionadas em especializações (subclasses) posteriores.

Da mesma maneira, uma referência do tipo Object só consegue acessar a porção referente a este tipo presente em objetos de qualquer classe.

Por conta da construção dos objetos em múltiplas camadas e do comportamento dos diferentes tipos de referências em relação às camadas presentes nos objetos, temos duas importantes operações decorrentes do polimorfismo:
  • up type casting (ou apenas upcasting) e
  • down type casting (ou apenas downcasting).
Mas este é o assunto do próximo post!

POO-P-22-Herança MúltiplaPOO-A-24-Polimorfismo, upcasting e downcasting

Referências Bibliográficas

[1] JAMSA, K.; KLANDER, L.. Programando em C/C++: a bíblia. São Paulo: Makron Books, 1999.
[2] PAGE_JONES, M.. Fundamentos do Desenho Orientado a Objeto com UML. São Paulo: Makron Books, 2001.
[3] SOMMERVILLE, I.. Software Engineering. 6th. Ed. Harlow: Pearson, 2001.
[4] DEITEL, H.M.; DEITEL, P.J.. Java: como programar. 6a. Ed. São Paulo: Pearson Prentice-Hall, 2005.
[5] SAVITCH, W.. C++ Absoluto. São Paulo: Pearson Addison-Wesley, 2004.
[6] JANDL JR., P. Introdução ao C++. São Paulo: Futura, 2003.
[7] JANDL JR., P.. Java - guia do programador. 3a. ed. São Paulo: Novatec, 2015.
[8] RUMBAUGH, J.; BLAHA, M.; PREMERLANI, W.; EDDY, F.; LORENSEN, W.. Object-oriented modeling and design. Englewoods Cliffs: Prentice-Hall, 1991.
[9] STROUSTRUP, B.. The C++ Programming Language. 3rd Ed. Reading: Addison-Wesley, 1997.
[10] LANGSAM, Y.; AUGENSTEIN, M. J.; TENENBAUM, A. M.. Data structures using C and C++. 2nd Ed. Upper Saddle River: Prentice-Hall, 1996.
[11] WATSON, K.; NAGEL, C.; PEDERSEN, J.H.; REID, J.D.; SKINNER, M.; WHITE, E.. Beginning Microsoft Visual C# 2008. Indianapolis: Wiley Publishing, 2008.
[12] GAMMA, E.; HELM, R.; JOHNSON, R.; VLISSIDES, J.. Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995.