Se
refiere a la posibilidad de enviar un mensaje a un grupo de objetos
cuya naturaleza puede ser heterogénea. El único requisito que deben
cumplir los objetos que se utilizan de manera polimórfica es saber
responder al mensaje que se les envía.
La
apariencia del código puede ser muy diferente dependiendo del
lenguaje que se utilice, más allá de las obvias diferencias
sintácticas.
Por
ejemplo, en un lenguaje de programación que cuenta con un sistema
de tipos
dinámico (en los que las variables pueden contener datos de
cualquier tipo u objetos de cualquier clase) como Smalltalk
no se requiere que los objetos que se utilizan de modo polimórfico
sean parte de una jerarquía de clases.
En
lenguajes basados en clases y con un sistema
de tipos
de datos fuerte (independientemente de si la verificación se realiza
en tiempo de compilación o de ejecución), es posible que el único
modo de poder utilizar objetos de manera polimórfica sea que
compartan una raíz común, es decir, una jerarquía de clases, ya
que esto proporciona la compatibilidad de tipos de datos necesaria
para que sea posible utilizar una misma variable de referencia (que
podrá apuntar a objetos de diversas subclases de dicha jerarquía)
para enviar el mismo mensaje (o un grupo de mensajes) al grupo de
objetos que se tratan de manera polimórfica.
En
Java,
es frecuente y profusamente aconsejada la utilización de interfaces
(que es un mecanismo del lenguaje que se emplea por medio de la
palabra clave Interface) para proveer la necesaria concordancia de
tipos para hacer posible el polimorfismo, también como un contrato
que debe cumplir cualquier clase que implemente una cierta interfaz y
como una forma de documentación para los desarrolladores. A veces,
en la literatura que refiere específicamente a Java se hace mención
a "herencia y polimorfismo de interfaces", lo que no
concuerda con los conceptos de la programación orientada a objetos
porque una clase que implementa una interfaz sólo obtiene su tipo de
datos y la obligación de implementar sus métodos, no obtiene
comportamiento ni de atributos. Esto muchas veces resulta paradójico
porque en Java frecuentemente se utiliza la mal llamada "herencia
de interfaces" para dotar a una clase con un tipo adicional (o
varios) para que su uso en combinación con la agregación
(colaboración o composición) permita evitar la necesidad de la
herencia múltiple y favorezca una utilización más amplia del
polimorfismo.
No
obstante, el uso de una jerarquía de clases como paso previo, es muy
habitual incluso en aquellos lenguajes en los que es posible
prescindir de tal jerarquía, ya que, desde una perspectiva
conceptual, se puede decir que al pertenecer los "objetos
polimórficos" a subclases de una misma jerarquía, se asegura
la equivalencia semántica de los mensajes que se invocarán de modo
polimórfico. Por esto, en programación orientada a objetos a veces
se denomina al polimorfismo como "polimorfismo de subclase (o de
subtipo)".
En
resumen, en la programación orientada a objetos, la esencia del
polimorfismo no atañe a la clase o prototipo de la que provienen los
objetos. Aun así, en los lenguajes basados en clases, es habitual (y
en algunos tal vez sea el único modo) que dichos objetos pertenezcan
a subclases pertenecientes a una misma jerarquía. Entonces, el
polimorfismo debe verse como una forma flexible de usar un grupo de
objetos (como si fueran sólo uno). Podría decirse que el
polimorfismo en esencia refiere al comportamiento de los objetos, no
a su pertenencia a una jerarquía de clases (o a sus tipos de datos).
Lo
anterior se hace aún más evidente en lenguajes de programación
orientada a objetos basados en prototipos, como Self,
en los que las clases no existen.
Además,
es importante remarcar que si un cierto grupo de objetos pueden
utilizarse de manera polimórfica es porque, en última instancia,
todos ellos saben responder a un cierto mensaje (o a varios), pero
dado que esos mismos objetos generalmente contendrán otros métodos
(que otros objetos en dicho grupo no contienen), difícilmente se
pueda decir lisa y llanamente que los objetos son polimórficos; lo
correcto es decir que esos objetos se pueden utilizar de modo
polimórfico para un cierto conjunto de mensajes.
Un
ejemplo. Podemos crear dos clases distintas: Pez y Ave que heredan de
la superclase
Animal. La clase
Animal tiene el método abstracto mover que se implementa de forma
distinta en cada una de las subclases (peces y aves se mueven de
forma distinta). Entonces, un tercer objeto puede enviar el mensaje
mover a un grupo de objetos Pez y Ave por medio de una variable de
referencia de clase Animal, haciendo así un uso polimórfico de
dichos objetos respecto del mensaje mover.
El
concepto de polimorfismo, desde una perspectiva más general, se
puede aplicar tanto a funciones como a tipos de datos. Así nacen los
conceptos de funciones polimórficas y tipos polimórficos. Las
primeras son aquellas funciones que pueden evaluarse o ser aplicadas
a diferentes tipos de datos de forma indistinta; los tipos
polimórficos, por su parte, son aquellos tipos de datos que
contienen al menos un elemento cuyo tipo no está especificado.
Clasificación
Se
puede clasificar el polimorfismo en dos grandes clases:
- Polimorfismo dinámico (o polimorfismo paramétrico) es aquél en el que el código no incluye ningún tipo de especificación sobre el tipo de datos sobre el que se trabaja. Así, puede ser utilizado a todo tipo de datos compatible.
- Polimorfismo estático (o polimorfismo ad hoc) es aquél en el que los tipos a los que se aplica el polimorfismo deben ser explicitados y declarados uno por uno antes de poder ser utilizados.
El
polimorfismo dinámico unido a la herencia es lo que en ocasiones
se conoce como programación
genérica.
También
se clasifica en herencia por redefinición de métodos abstractos
y por método sobrecargado. El segundo hace referencia al mismo
método con diferentes parámetros.
Otra
clasificación agrupa los polimorfismo en dos tipos: Ad-Hoc
que incluye a su vez sobrecarga de operadores y coerción,
Universal (inclusión o controlado por la herencia, paramétrico o
genericidad).
Polimorfismo desde una interfaz
Aunque
el polimorfismo es el mismo se aplique donde se aplique, el modo en
que se aplica desde una interfaz
puede resultar un poco más oscuro y difícil de entender. Se expone
un sencillo ejemplo (en VB-NET) comentado para entender como funciona
aplicado desde una interfaz, primero se escribe el código y luego se
comenta el funcionamiento. Nota: para no enturbiar el código en
exceso, todo lo que no se declara privado se sobreentiende público.
' Declaramos una interfaz llamada IOperar y declaramos una función llamada Operar
' que implementarán las clases deseadas:
Interface IOperar
Function Operar(valor1 as integer, valor2 as integer) as long
End Interface
' Declaramos una clase que trabaja más alejada del usuario y que contendría funciones comunes
' para las siguiente clase, si no fueran idénticas irían en la interfaz,
' pero al caso de mostrar el polimorfismo se suponen idénticas:
Class Operacion
Function Calcular(clasellamante as Object) as Long
' aquí iría el código común a todas las operaciones.... que llaman a esa función
' por ejemplo recoger los 2 valores de la operación, chequear que están en el rango deseado, etc.
' se supone que la función inputValor recoge un valor de algún sitio
valor1 as integer = inputValor()
valor2 as integer = inputValor()
op as New IOperar = clasellamante
Return op.Operar(valor1,valor2) 'AQUÍ es donde se utiliza el polimorfismo.
End Function
End Class
' Declaramos 2 clases: Sumar y Restar que implementan la interfaz y que llaman a la clase Operacion:
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) ' se está llamando a la función 'Calcular' de la clase 'Operación'
End Function
End Class
' segunda clase....
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) ' se está llamando a la función 'Calcular' de la clase 'Operación'
End Function
End Class
Analicemos
ahora el código para entender el polimorfismo expuesto en la
interfaz: La interfaz expone un método que puede ser implementado
por las diferentes clases, normalmente relacionadas entre si. Las
clases Sumar y Restar implementan la interfaz pero el método de la
interfaz lo declaramos privado para evitar ser accedido libremente y
además tienen un método llamado Calcular que llama a la clase
Operacion donde tenemos otro método con el mismo nombre. Es esta
clase última la que realiza el polimorfismo y debe fijarse como es a
través de una instancia de la interfaz que llama al método operar.
La interfaz sabe a qué método de qué clase llamar desde el momento
que asignamos un valor a la variable OP en el método Calcular de la
clase Operacion, que a su vez recibió la referencia del método
Calcular desde la clase que la llama, sea ésta cual sea, se
identifica a sí misma, mediante la referencia Me ó This según el
lenguaje empleado. Debe notarse que la instancia de la interfaz
accede a sus métodos aunque en sus clases se hayan declarado
privadas.
Diferencias entre polimorfismo y sobrecarga
El
polimorfismo como se muestra en el ejemplo anterior, suele ser
bastante ventajoso aplicado desde las interfaces, ya que permite
crear nuevos tipos sin necesidad de tocar las clases ya existentes
(imaginemos que deseamos añadir una clase Multiplicar),
basta con recompilar todo el código que incluye los nuevos tipos
añadidos. Si se hubiera recurrido a la sobrecarga
durante el diseño
exigiría retocar la clase anteriormente creada al añadir la nueva
operación Multiplicar,
lo que además podría suponer revisar todo el código donde se
instancia a la clase.
- La sobrecarga se da siempre dentro de una sola clase, mientras que el polimorfismo se da entre clases distintas.
- Un método está sobrecargado si dentro de una clase existen dos o más declaraciones de dicho método con el mismo nombre pero con parámetros distintos, por lo que no hay que confundirlo con polimorfismo.
- En definitiva: La sobrecarga se resuelve en tiempo de compilación utilizando los nombres de los métodos y los tipos de sus parámetros; el polimorfismo se resuelve en tiempo de ejecución del programa, esto es, mientras se ejecuta, en función de la clase a la que pertenece el objeto
No hay comentarios:
Publicar un comentario