Publicado em Programação

Polimorfismo

polimorfismo3

Pressupostos

Para compreender o texto que preparei são necessário conhecimentos elementares sobre : classes, estruturas de dados e herança.

Apontadores para a classe base

Um dos aspectos chave da derivação de classes resulta do facto de um apontador para uma classe derivada ser de tipo compatível com o apontador da sua classe base. O polimorfismo é a arte de tirar proveito desta característica, que sendo simples é bastante poderosa, o que conduz as metodologias Orientadas a Objectos ao máximo do seu potencial.
Vamos começar por reescrever o nosso programa acerca do triâgulo e do rectângulo utilizado na secção anterior mas tendo em consideração esta propriedade de compatibilidade entre apontadores:

// pointers to base class
#include <iostream>
using namespace std;

class CPolygon {
public:
	void set_values (int a, int b){
		width=a;
		height=b;
	}
protected:
	int width, height;
};

class CRectangle: public CPolygon{
public:
	int area (){
		return (width * height);
	}
};

class CTriangle: public CPolygon{
public:
	int area (){
		return (width * height / 2);
	}
};

int main (){
	CRectangle rect;
	CTriangle trgl;
	CPolygon * ppoly1 = &rect;
	CPolygon * ppoly2 = &trgl;
	ppoly1->set_values (4,5);
	ppoly2->set_values (4,5);
	cout << rect.area() << endl;
	cout << trgl.area() << endl;
	return 0;
}

Na função main, criamos dois apontadores que apontam para objectos da classe CPoligon (ppoly1 e ppoly2). De seguida atribuímos as referências a rect e trgl para estes apontadores, e como ambos são objectos da classe derivada de CPolygon, ambas são operações de atribuição válidas.
A única limitação de utilizar ppoly1 e ppoly2 em vez de rect e trgl é que ambos ppoly1 e ppoly2 são do tipo CPolygon* e, portanto, apenas podemos utilizar estes apontadores para nos referirmos aos membros que as classes CRectangle e CTriangle herdam de CPoligon. É por esta razão que quando chamamos os membros area() no final do programa tivemos que utilizar directamente os objectos rect e trgl em vez dos apontadores *ppoly1 e *ppoly2.
De forma a podermos utilizar a função area() com os apontadores para a classe CPoligon, este membro também deveria ter sido declarado na classe CPolygon, e não exclusivamente na sua classe derivada. O problema é que CRectangle e CTriangle implementam diferentes versões da função area(), por isso não podemos implementa-las na classe base. É aqui que os membros virtuais se vão tornar úteis.

Membros virtuais
Um membro de uma classe que pode ser redefinido na sua classe derivada é designado de membro virtual. Para declararmos um membro de uma classe como virtual devemos preceder a sua declaração com a palavra virtual:

// virtual members
#include <iostream>
using namespace std;

class CPolygon{
public:
	void set_values (int a, int b){
		width=a;
		height=b;
	}
	virtual int area (){
		return (0);
	}
protected:
	int width, height;
};

class CRectangle: public CPolygon {
public:
	int area (){
		return (width * height);
	}
};

class CTriangle: public CPolygon {
public:
	int area (){
		return (width * height / 2);
	}
};

int main () {
	CRectangle rect;
	CTriangle trgl;
	CPolygon poly;
	CPolygon * ppoly1 = &rect;
	CPolygon * ppoly2 = &trgl;
	CPolygon * ppoly3 = &poly;
	ppoly1->set_values (4,5);
	ppoly2->set_values (4,5);
	ppoly3->set_values (4,5);
	cout << ppoly1->area() << endl;
	cout << ppoly2->area() << endl;
	cout << ppoly3->area() << endl;
	return 0;
}

Agora as três classes (CPolygon, CRectangle e CTriangle) possuem todas os mesmos membros: width, height, set_values() e area().
A função membro area() foi declarada como virtual na classe-base pois será redefinida mais tarde na classe derivada. Isto pode ser verificado removendo a palavra reservada virtual da declaração de area() na classe CPoligon, e de seguida executando o programa e verificando que o resultado é 0 para os três polígonos, em vez de 20, 10 e 0. Isto acontece porque em vez de  chamarmos a função area() correspondente para cada objecto (CRectangle::area(), CTriangle::area() e CPolygon::area() respectivamente), CPolygon::area() será chamado em todos os casos,  uma vez que as invocações são feitas através dos apontadores do tipo CPolygon *.
Por isso o que a palavra reservada virtual faz, é permitir que um membro de uma classe derivada com o mesmo nome de uma pertencente à classe-base seja invocada de forma apropriada a partir do apontador, e mais precisamente quando o tipo do apontador é um apontador para a classe base mas que aponta para um objecto da classe derivada, tal como mostrado no exemplo acima.
Uma classe onde é declarada ou herdada uma função virtual é designada de classe polimórfica.
Note que apesar da sua natureza virtual, também nos foi possível declarar um objecto do tipo CPoligon e invocar a sua função area(), que devolve sempre 0.

Classes base abstractas

As classe base abstractas são muito semelhantes à nossa classe CPoligon do exemplo anterior. A única diferença reside no facto de  no caso anterior termos definido uma função area() válida, com o mínimo de funcionalidades para os objectos que eram da classe CPoligon ( como o objecto poly), enquanto que numa classe base abstracta podíamos deixar essa função membro area() sem qualquer implementação. Isto é feito adicionando =0 (igual a zero) à declaração da função.
Uma classe base abstracta CPolygon poderia ter este aspecto:

// abstract class CPolygon
class CPolygon {
public:
	void set_values (int a, int b) {
		width=a; height=b;
	}
    virtual int area () =0;
protected:
	int width, height;
};

Note como adicionamos =0 a virtual int area () em vez de especificar uma implementação para a função. Este tipo de função é designado de função virtual pura, e todas as classes que contenham pelo menos uma função deste tipo são consideradas como classe base abstracta.
A principal diferença entre classes base abstractas e classes polomórficas comuns resulta do facto de nas classes base abstractas não podermos criar instâncias (objectos) a partir delas, pois pelo menos uma das suas funções membro não possui implementação.
De qualquer das formas uma classe a partir da qual não é possível instanciar objectos não é totalmente inútil. Podemos criar apontadores para ela e tirar vantagens das suas potencialidades polimórficas. Por esta razão uma declaração como:
CPolygon poly;
Não seria válida para a classe base abstracta que acabamos de criar, porque tenta instanciar um objecto. No entanto, os seguintes apontadores:
CPolygon * ppoly1;
CPolygon * ppoly2;
Seriam perfeitamente válidos.
Isto só acontece enquanto CPolygon contiver uma função virtual pura e no entanto trata-se de uma classe base abstracta. De qualquer das formas, os apontadores para esta classe base abstracta podem apontar para objectos na classe derivada.
Segue-se um exemplo completo:

// abstract base class
#include <iostream>
using namespace std;

class CPolygon {
public:
	void set_values (int a, int b){
		width=a;
		height=b;
	}
    virtual int area (void) =0;
protected:
	int width, height;
};

class CRectangle: public CPolygon {
public:
	int area (void) {
		return (width * height);
	}
};

class CTriangle: public CPolygon {
public:
	int area (void) {
		return (width * height / 2);
	}
};

int main () {
	CRectangle rect;
	CTriangle trgl;
	CPolygon * ppoly1 = &rect;
	CPolygon * ppoly2 = &trgl;
	ppoly1->set_values (4,5);
	ppoly2->set_values (4,5);
	cout << ppoly1->area() << endl;
	cout << ppoly2->area() << endl;
	return 0;
}

Se analisarmos o programa atentamente iremos certamente notar que é feita referência a objectos diferentes mas de classes relacionadas, recorrendo apenas a um único tipo de apontador( CPoligon*). Isto pode extraordinariamente útil. Por exemplo, agora podemos criar uma função membro na classe base abstracta CPoligon que escreva no ecrã o resultado da função area() mesmo apesar da própria CPoligon não possuir uma implementação para esta função:

// pure virtual members can be called
// from the abstract base class
#include <iostream>
using namespace std;

class CPolygon {
public:
	void set_values (int a, int b) {
		width=a;
		height=b;
	}
    virtual int area (void) =0;
    void printarea (void) {
		cout << this->area() << endl; }
protected:
	int width, height;
};

class CRectangle: public CPolygon {
public:
	int area (void) {
		return (width * height);
	}
};

class CTriangle: public CPolygon {
public:
	int area (void) {
		return (width * height / 2);
	}
};

int main () {
	CRectangle rect;
	CTriangle trgl;
	CPolygon * ppoly1 = &rect;
	CPolygon * ppoly2 = &trgl;
	ppoly1->set_values (4,5);
	ppoly2->set_values (4,5);
	ppoly1->printarea();
	ppoly2->printarea();
	return 0;
}

Os membros virtuais e as classes abstractas concedem à linguagem C++ as características polimórficas que tornam a programação orientada a objectos um instrumento de enorme utilidade em projectos de grandes dimensões. Claro que os exemplos apresentados são aplicações muito simples destas características, que têm aplicação em arrays de objectos ou objectos alocados dinamicamente.
Vamos terminar com o mesmo exemplo, mas desta vez com alocação dinâmica de memória:

// dynamic allocation and polymorphism
#include <iostream>
using namespace std;

class CPolygon {
public:
	void set_values (int a, int b) {
		width=a;
		height=b;
	}
    virtual int area (void) =0;
    void printarea (void) {
		cout << this->area() << endl;
	}
protected:
	int width, height;
};

class CRectangle: public CPolygon {
public:
	int area (void) {
		return (width * height);
	}
};

class CTriangle: public CPolygon {
public:
	int area (void) {
		return (width * height / 2);
	}
};

int main () {
	CPolygon * ppoly1 = new CRectangle;
	CPolygon * ppoly2 = new CTriangle;
	ppoly1->set_values (4,5);
	ppoly2->set_values (4,5);
	ppoly1->printarea();
	ppoly2->printarea();
	delete ppoly1;
	delete ppoly2;
	return 0;
}

Notar que os apontadores ppoly1 e ppoly2:
CPolygon * ppoly1 = new CRectangle;
CPolygon * ppoly2 = new CTriangle;
são declarados como sendo do tipo apontadore para Cpolygon mas os objectos alocados dinamicamente foram declarados directamente como sendo do tipo da classe derivada.
Nota final:
Este artigo resultou da necessidade de utilizar um texto que resumisse o essencial sobre esta questão do polimorfismo, e que estivesse escrito em Português.
Depois de alguma pesquisa seleccionei um artigo de Juan Soulie que encontrei em  http://www.cplusplus.com/doc/tutorial/polymorphism.html .
Não sou nenhum especialista em tradução mas julgo que dá para entender a mensagem, e este artigo está de facto muito interessante.
A imagem inicial foi inspirada nouto pequeno artigo ( http://www.vivaolinux.com.br/artigo/Polimorfismo-Uma-visao-em-C++-e-Java ) que também considero muito engraçada para introduzir esta questão do polimorfismo. Achei importante alterar as setas devido às regras de representação de Diagramas de Classes e aproveitei para mudar a bicharada para algo mais Lusitano.
O meu muito obrigado a todos os que partilharam o conhecimento que eu utilizei neste trabalho.

Anúncios

Deixe uma Resposta

Preencha os seus detalhes abaixo ou clique num ícone para iniciar sessão:

Logótipo da WordPress.com

Está a comentar usando a sua conta WordPress.com Terminar Sessão / Alterar )

Imagem do Twitter

Está a comentar usando a sua conta Twitter Terminar Sessão / Alterar )

Facebook photo

Está a comentar usando a sua conta Facebook Terminar Sessão / Alterar )

Google+ photo

Está a comentar usando a sua conta Google+ Terminar Sessão / Alterar )

Connecting to %s