Vés al contingut

Polimorfisme (programació)

De la Viquipèdia, l'enciclopèdia lliure
Exemple de polimorfisme.

El polimorfisme (del Grec πολύς, polys, "molt, molts" i μορφή, morphē, "forma, figura") és una característica d'alguns llenguatges de programació que tenen la propietat d'enviar missatges sintàcticament iguals als objectes de diferents tipus. L'únic requisit que han de complir els objectes que s'utilitzen de manera polimòrfica és saber respondre al missatge que se'ls hi envia.

L'aparença dels codi pot ser molt diferent depenent del llenguatge que s'utilitzi, més enllà de les òbvies diferències sintàctiques.

Hi ha dos tipus genèrics de polimorfisme: el polimorfisme de sobre-càrrega (overload) i el polimorfisme de sobre-escriptura (override).

Descripció

[modifica]

En el llenguatge basat en classes i amb un sistema de tipus de dades fort (independentment de si la verificació es realitza en temps de compilació o d'execució), és possible que l'única manera de poder utilitzar objectes de manera polimòrfica sigui que comparteixin una arrel comuna, és a dir, una jerarquia de classes, ja que això proporciona la compatibilitat de tipus de dades necessària perquè sigui possible utilitzar una mateixa variable de referència (que podrà apuntar a objectes de diverses subclasses d'aquesta jerarquia) per enviar el mateix missatge (o un grup de missatges) al grup d'objectes que es tracten de manera polimòrfica.

No obstant això, alguns llenguatges de programació (Java, C ++) permeten que dos objectes de diferents jerarquies de classes responguin als mateixos missatges, a través de les anomenades interfícies (aquesta tècnica es coneix com a composició d'objectes). Dos objectes que implementin la mateixa interfície podran ser tractats de forma idèntica, com un mateix tipus d'objecte.

En la programació orientada a objectes, l'essència del polimorfisme no afecta a la classe de la qual provenen els objectes. Tot i així, en els llenguatges basats en classes, és habitual (i en alguns potser és l'única manera) que aquests objectes pertanyen a subclasses que pertanyen a una mateixa jerarquia. Llavors, el polimorfisme s'ha de veure com una forma flexible d'usar un grup d'objectes (com si fossin només un). Es podria dir que el polimorfisme en essència es refereix al comportament dels objectes, no a la seva pertinença a una jerarquia de classes (o als seus tipus de dades).

Un exemple. Podem crear dues classes diferents: Peix i Au que hereten de la superclasse Animal. La classe Animal té el mètode abstracte moure que s'implementa de manera diferent en cadascuna de les subclasses (peixos i aus es mouen de manera diferent). Llavors, un tercer objecte pot enviar el missatge moure a un grup d'objectes PeixAu per mitjà d'una variable que faci referència a la classe Animal, fent així un ús polimòrfic d'aquests objectes respecte al missatge moure.

El concepte de polimorfisme, des d'una perspectiva més general, es pot aplicar tant a funcions com a tipus de dades. Així neixen els conceptes de funcions polimòrfiques i tipus polimòrfics. Les primeres són aquelles funcions que poden avaluar-se o ser aplicades a diferents tipus de dades de forma indistinta; els tipus polimòrfics, per la seva banda, són aquells tipus de dades que contenen almenys un element el qual el tipus no està especificat.

Classificació

[modifica]

Es pot classificar el polimorfisme en quatre grans tipus:

Polimorfisme ad hoc

[modifica]

El polimorfisme ad hoc (o polimorfisme estàtic) és aquell en què el codi necessita el tipus de dades amb les quals es treballa, és a dir, els tipus de dades han de ser explícits i declarats un per un abans de poder ser utilitzats.

Polimorfisme paramètric

[modifica]

El polimorfisme paramètric (o polimorfisme dinàmic) és aquell en què el codi no inclou cap tipus d'especificació sobre el tipus de dades sobre les quals treballa, per tant, pot ser utilitzada transparentment amb qualsevol nombre de nous tipus. La independència envers les dades provoca que pot ser utilitzat per qualsevol tipus de dades compatible. En programació orientada a objectes, se'n sol dir programació genèrica. En la comunitat de programació, aquest és el polimorfisme més habitual i se'n sol dir simplement polimorfisme.

El polimorfisme dinàmic unit a l'herència es coneix com a programació genèrica.

El concepte de polimorfisme paramètric s'aplica tant a tipus de dades com a funcions. Una funció que pot ser avaluada o ser aplicada als valors dels diferents tipus es coneix com una funció polimòrfica. Un tipus de dades que poden semblar d'un tipus generalitzat (per exemple, una llista amb els elements de tipus arbitrari) es designa tipus de dades polimòrfic igual que el tipus generalitzat de què estan fetes aquestes especialitzacions.

El següent exemple mostra un tipus de dades llista parametritzada i dues funcions de forma paramètricament polimòrfiques entre elles:

data List a = Nil | Cons a (List a)
length :: List a -> Integer
length Nil = 0
length (Cons x xs) = 1 + length xs
map :: (a -> b) -> List a -> List b
map f Nil = Nil
map f (Cons x xs) = Cons (f x) (map f xs)

El polimorfisme paramètric també està disponible en diversos llenguatges orientats a objectes, on sovint es coneix amb el nom de "genèrics" (per exemple, Java) o "plantilles" (C ++):

class List<T> {
class Node<T> {
 T elem;
  Node<T> next;
 }
  Node<T> head;
int length() { ... }
}

 List<B> map(Func<A,B> f, List<A> xs) {
 ...
 }

Subtipatge

[modifica]

Alguns llenguatges utilitzen la idea de subtipatge per a restringir la gamma de tipus que es poden utilitzar en un cas particular de polimorfisme. En aquests llenguatges, el polimorfisme de subtipus (de vegades conegut com a polimorfisme d'inclusió o polimorfisme dinàmic) permet una funció per a ser escrit per prendre un objecte d'un tipus determinat T, però també funcionarà correctament si s'aprova un objecte que pertany a un tipus S que és un subtipus de T (d'acord amb el principi de substitució Liskov). Aquesta relació de tipus de vegades s'escriu S <: T. Per contra, T es diu que és un Supertipus de S escrit T :> S.

En el següent exemple fem gats i gossos subtipus d'animals. El procediment escoltem() accepta un animal, però també funcionarà correctament si es passa un subtipus:

abstract class Animal {
abstract String parla();
}
class Gat extends Animal {
String parla() {
return "Miau!";
}
}
class Gos extends Animal {
String parla() {
return "Guau!";
}
}
void escoltem(Animal a) {
println(a.parla());
}
void main() {
escoltem(new Gat());
escoltem(new Gos());
}


En un altre exemple, si els naturals, racionals, i enters són tipus tals que Natural:> Racional i Natural:> Enter, una funció escrita per tenir un nombre funcionarà igualment bé quan es passa un enter o racional com quan es passa d'un natural. El tipus real de l'objecte es pot amagar dels clients en una "capsa", i s'accedeix a través de la identitat de l'objecte. De fet, si el tipus de nombre és abstracta, és possible que ni tan sols sigui possible per aconseguir les seves mans en un objecte el tipus més derivat sigui natural. Aquest tipus particular de jerarquia de tipus es coneix, especialment en el context del "Llenguatge de programació en Esquema", com una torre numèrica, i en general conté molts més tipus.

Llenguatges de programació orientats a objectes ofereixen subtipatge polimòrfic utilitzant subclasses (també conegut com a herència). En implementacions típiques, cada classe conté el que s'anomena una taula virtual, una taula de funcions que implementen la part polimòrfica de la classe d'interfície i cada objecte conté un punter a la "vtable" de la seva classe, que és consultat a continuació, cada vegada que s'invoca un mètode polimòrfic.

Dades genèriques

[modifica]

....

Exemple de polimorfisme

[modifica]

En el següent exemple fem ús del llenguatge C++ per il·lustrar el polimorfisme. S'observa a la vegada l'ús de les funcions virtuals pures (funcions que constitueixen una interfície més consistent quan es treballa amb una jerarquia de classes), ja que fan possibles l'enllaç durant l'execució. No obstant això, perquè el polimorfisme funcioni, no és condició necessària que totes les funcions de la classe base estiguin declarades com virtual.

#include<iostream>
using namespace std;

class Figura {
 private:
 float base;
 float altura; 
 public:
 void captura();
 virtual unsigned float perimetre()=0;
 virtual unsigned float area()=0;
};

class Rectangle: public Figura { 
 public:
 void imprimeix();
 unsigned float perimetre(){return 2*(base+altura);}
 unsigned float area(){return base*altura;}
};

class Triangle: public Figura {
 public:
 void mostra();
 unsigned float perimetre(){return 2*altura+base}
 unsigned float area(){return (base*altura)/2;}
};

void Figura::captura()
{
 cout << "CALCÚL DE L'ÀREA I EL PERIMETRE D'UN TRIANGLE ISÒCELES I UN RECTANGLE:" << endl;
 cout << "Escriu l'altura: ";
 cin >> altura;
 cout << "Escriu la base: ";
 cin >> base;
 cout << "EL PERIMETRE ÉS: " << perimetre();
 cout << "L'ÀREA ÉS: " << area();
getchar();
return 0;
}

Polimorfisme des d'una interfície

[modifica]

Tot i que el polimorfisme és el mateix s'apliqui on s'apliqui, la manera en què s'aplica des d'una interfície pot resultar una mica més fosc i difícil d'entendre. S'exposa un senzill exemple (en VB-NET) comentat per entendre com funciona aplicat des d'una interfície, primer s'escriu el codi i després es comenta el funcionament.

Nota: per no enterbolir el codi en excés, tot el que no es declara privat se sobreentén que és públic.

 ' Declarem una interfície anomenada IOperar i declarem la funció Operar que implementaran les classes desitjades: 
 Interface IOperar
 Function Operar(valor1 as integer, valor2 as integer) as long
 End Interface
 ' Declarem una classe que treballa més allunyada de l'usuari i que contindrà funcions comunes per les següents classes.
 ' Si no fossin idèntiques haurien d'anar a la interfície.
 Class Operació
 Function Calcular(classecrida as Object) as Long 
 ' Aquí s'hauria de dir el codi comú a totes les operacions que criden a aquesta funció.

 ' Se suposa que la funció inputValor recull un valor d'algun lloc.
 valor1 as integer = inputValor() 
 valor2 as integer = inputValor()

 op as New IOperar = classecrida
 Return op.Operar(valor1,valor2) ' Aquí és on s'utilitza el polimorfisme.
 End Function
 End Class
 ' Declarem 2 classes: Sumar i Restar que implementen la interfície i que criden a la classe Operació:
 Class Sumar
 Implements IOperar
 Private Function Operar(valor1 as Integer, valor2 as Integer) as Long Implements IOperar.Operar
 Return valor1 + valor2
 End Function

 Function Calcular() as Long
 op as New operacion
 Return op.Calcular(Me) ' es crida a la funció 'Calcular' de la classe 'Operació'
 End Function
 End Class
 ' Segona classe.
 Class Restar
 Implements IOperar
 Private Function Operar(valor1 as Integer, valor2 as Integer) as Long Implements IOperar.Operar
 Return valor1 - valor2
 End Function

 Function Calcular() as Long
 op as New operacion
 Return op.Calcular(Me) ' es crida a la funció 'Calcular' de la classe 'Operació'
 End Function
 End Class

Analitzem ara el codi per a entendre el polimorfisme exposat a la interfície: La interfície exposa un mètode que pot ser implementat per les diferents classes, normalment relacionades entre si. Les classes Sumar i Restar implementen la interfície però el mètode de la interfície el declarem privat per evitar ser accedit lliurement ia més tenen un mètode anomenat Calcularque crida a la classe Operacióon tenim un altre mètode amb el mateix nom. És aquesta classe última la que realitza el polimorfisme i ha de fixar-se com és a través d'una instància de la interfície que crida al mètode operar. La interfície sap a quin mètode de quina classe trucar des del moment que assignem un valor a la variable OP al mètode Calcular de la classe Operació, que al seu torn va rebre la referència del mètode Calcular des de la classe que la flama, sigui quina sigui, s'identifica a si mateixa, mitjançant la referència This segons el llenguatge emprat. Cal notar que la instància de la interfície accedeix als seus mètodes encara que en les seves classes s'hagin declarat privades.

Polimorfisme de sobre-càrrega i de sobre-escriptura

[modifica]

El polimorfisme de sobre-càrrega, consisteix a implementar diverses vegades un mateix mètode però amb paràmetres diferents, de tal manera que en invocar-lo, el compilador decideix quin dels mètodes s'ha d'executar, en funció dels paràmetres de la crida.

També s'anomena polimorfisme de sobrecàrrega si una funció actua sobre el mateix tipus de variables. Aquest polimorfisme el suporten la gran majoría de llenguatges.

(Per exemple: es pot fer "i + j" amb "ints" o "floats")

Un altre exemple:

int entraElTeuPes (int pes) { .... codi1 .... }
int entraElTeuPes (String pes) { .... codi2 .... }
void funcio () {
entraElTeuPes (65); // Invoca el primer mètode executant el "codi1".
entraElTeuPes ("65"); // Invoca el segon mètode executant el "codi2".
}

El polimorfisme de sobre-escriptura, consisteix en reimplementar un mètode heretat d'una superclasse amb exactament la mateixa definició (incloent nom de mètode, paràmetres i valor de retorn). Això permet que en funció de la classe de pertinença d'un objecte, el compilador determini quin dels mètodes ha d'executar. (Recorda que la classe de pertinença correspon a la classe de la qual s'ha invocat el mètode constructor mitjançant la sentència "new").

class Clase1 {
metode1 () { ... codi 1 ... } }
class Clase2 extends Clase1 () {
metode1 () { ... codi 2 ... } }
Clase1 objecte1 = new Clase1 ();
objecte1.metode1 (); // Invoca el primer mètode executant el "codi 1".
Clase1 objecte2 = new Clase2 ();
objecte2.metode1 (); // Invoca el segon mètode executant el "codi 2".