Polimorfisme (programació)

De Viquipèdia
Dreceres ràpides: navegació, cerca

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 obvies 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 | modifica el codi]

En el llenguatge basat en classes i amb un sistema de tipus de dades fort (independentent 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,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 el tipus definit per la interfície.

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 superclase 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 fagi 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 | modifica el codi]

Es pot classificar el polimorfisme en quatre grans tipus:

Poliformisme ad hoc[modifica | modifica el codi]

El poliformisme ad hoc (o poliformisme 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 explicits i declarats un per un abans de poder ser utilitzats.

Poliformisme paramètric[modifica | modifica el codi]

El poliformisme paramètric (o poliformisme 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 communitat de programació, aquest és el polimorfisme més habitual i se'n sol dir simplement polimorfisme.

El poliformisme dinàmic unit a la herència es coneix com programació genèrica.

El concepte de polimorfisme paramètric s'aplica a tots dos tipus de dades i funcions. Una funció que pot avaluar o ser aplicada als valors dels diferents tipus es coneix com una funció polimòrfica. Un tipus de dades que poden semblar ser d'un tipus generalitzat (per exemple, una llista amb els elements de tipus arbitrari) es designa com tipus de dades polimòrfic com 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 | modifica el codi]

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 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 escoltem() procediment 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 número 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 | modifica el codi]

....

Exemple de poliformisme[modifica | modifica el codi]

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 interface 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 interface[modifica | modifica el codi]

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 interface anomenada IOperar y 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 identiques haurien d'anar a la interficie. 
  Class Operació
  Function Calcular(classecrida as Object) as Long 
  ' Aqui 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) ' Aqui és on s'utilitza el polimorfisme.
  End Function
  End Class
  ' Declarem 2 classes: Sumar y Restar que implementen la interface 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 tot i que en les seves classes s'hagin declarat privades.

Polimorfisme de sobre-càrrega i de sobre-escriptura[modifica | modifica el codi]

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); // Invonca el primer mètode executant el "codi1".
entraElTeuPes ("65"); // Invonca el segon mètode executant el "codi2".
}

El polimorfisme de sobre-escriptura, consisteix en re-implementar 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".