gravatar

Conceptos básicos de la POO - Parte 7



Polimorfismo

Tal vez la forma más sencilla de expresar en qué consiste el tipo de polimorfismo que enfatiza la programación orientada a objetos, sea por medio de los conceptos de objeto y mensaje:

Dos (o más) objetos son polimórficos para un tercero cuando éste les envía el mismo mensaje a cada uno y ambos lo comprenden.

Que ambos objetos entiendan ese mensaje implica que existe una equivalencia semántica entre los métodos que implementan dicho mensaje en cada objeto. Es decir, que en un nivel de abstracción más alto su significado es el mismo.

La única manera en que se puede asegurar la equivalencia semántica de ese mensaje en ambos objetos es que pertenezcan a una misma jerarquía de clases y es por eso que hablamos de polimorfismo de subclase.

Según los paradigmas que admita un lenguaje puede que incluya otros tipos de polimorfismo. Además, en los lenguajes que sólo admiten la POO puede haber variantes en la forma de escribir programas que utilicen polimorfismo dependiendo de si son dinámica o estáticamente tipados..

Veamos un ejemplo típico. Representaremos dos especies animales: gatos y perros, mediante un nombre y un comportamiento que consistirá en emitir el sonido típico mediante el que estos animales se comunican. Pero para simplificar el ejemplo, reemplazaremos la emisión de los sonidos por el envío de un mensaje a la salida estándar. Ya que los perros se comunican ladrando y los gatos maullando, dicho comportamiento consistirá en enviar a la consola las onomatopeyas de estos animalitos.

De modo que deberemos declarar una clase Gato y una clase Perro. Pero debido a que las clases Gato y Perro pueden generalizarse, definiremos también la clase Animal.

'Clase Animal PRIVATE $nombre AS String PUBLIC SUB _new(nombre AS String) $nombre = nombre END PUBLIC SUB getNombre() AS String Return $nombre END PUBLIC SUB habla() END

La clase Animal encapsula las características comunes de las clases Gato y Perro. Observe que al generalizar en vez de hablar de que los gatos maúllan y de que los perros ladran decimos, figuradamente, que hablan.

Debido a que definiremos las clases Gato y Perro como subclases de Animal, los gatos y los perros de nuestra aplicación también hablarán, cada uno a su manera. Observe que así aseguramos la equivalencia semántica del mensaje habla() o dicho de otro modo, nos aseguramos que tanto los objetos de tipo Gato como los objetos de tipo Perro comprendan el mensaje habla().

'Clase Gato INHERITS Animal PUBLIC SUB habla() PRINT ME.getNombre() & ": Miau" END

'Clase Perro INHERITS Animal PUBLIC SUB habla() PRINT ME.getNombre() & ": Guau" END

Las clases Gato y Perro re-definen el método heredado habla() porque los perros y los gatos "hablan" en modos diferentes, pero en un nivel de abstracción más alto (la generalización en la clase Animal) el mensaje tiene un significado equivalente.

Observe que para acceder a los métodos heredados se utiliza la palabra clave ME.

Hasta aquí, hicimos uso de la herencia como una condición fundamental para usar el polimorfismo. A continuación, usaremos las clases Gato y Perro de forma polimórfica.

' Gambas module file 'Si usa Gambas 2.2x deberá reemplazar el tipo Animal[] 'por Object[] PRIVATE animales AS NEW Animal[] PUBLIC SUB main() DIM unAnimal, fido, pluto, tom, silvestre AS Animal fido = NEW Perro("Fido") pluto = NEW Perro("Pluto") tom = NEW Gato("Tom") silvestre = NEW Gato("Silvestre") animales.Add(fido) animales.Add(pluto) animales.Add(tom) animales.Add(silvestre) FOR EACH unAnimal IN animales unAnimal.habla() NEXT END

Deténgase a observar que las variables unAnimal, fido, pluto, tom y silvestre se declaran de tipo Animal, pero se les asigna un objeto de tipo Gato o de tipo Perro. En un lenguaje de tipos estáticos, generalmente, el tipo de una variable queda determinado en su declaración. Por ejemplo, una variable de tipo Byte sólo puede contener valores enteros en un rango determinado (generalmente -128 a 127 o 0 a 255). Sin embargo, a las variables de tipo Animal fue posible asignarles objetos de tipo Gato y de tipo Perro.

Piense otra vez. ¿Acaso las variables de referencia no contienen siempre una dirección de memoria independientemente de la clase que se haya declarado como su tipo? Sí, pero recuerde también lo que se mencionó antes, al definir el concepto de clase, respecto de que una clase es también un tipo y que el compilador o intérprete les otorga el mismo tratamiento que a los tipos nativos del lenguaje. De modo que, el intérprete de Gambas verifica en tiempo de ejecución que no se utilice una variable de referencia declarada de una clase para crear objetos de otra clase, excepto que se trate de un sub-tipo como es el caso de Gato y Perro con respecto a Animal.

Esta es la salida del programa:

Fido: Guau
Pluto: Guau
Tom: Miau
Silvestre: Miau

Note que todos los objetos cuyas clases pertenecen a una jerarquía son potencialmente polimórficos dependiendo de cómo se los use.

Observe, además, que la ejecución del método habla() se realiza a partir de la variable de referencia unAnimal que es de tipo Animal, pero el método habla() que se ejecuta no es el que se definió en la clase Animal, sino el que se definió en la clase Perro o en la clase Gato. Sucede así precisamente porque al crear los objetos (fido, pluto, ...) su tipo no es Animal, sino Perro o Gato, de modo que en cada caso se llama al método habla() que corresponde.

Véalo de este modo, cuando dentro del bucle FOR EACH se ejecuta el primer método unAnimal.habla(), Gambas observa que el objeto referenciado mediante unAnimal es de tipo Perro (que se creó mediante la variable de referencia fido de tipo Animal) y ejecuta el método habla() redefinido por la clase Perro, en lugar de ejecutar el método habla() de la clase Animal.

Sin embargo, debe tomar nota de que al ser la variable unAnimal de tipo Animal, permite ejecutar los métodos definidos en dicha clase, pero no los métodos definidos en la subclase a la que pertenece el objeto.

¿Le parece confuso? Vamos otra vez. Mediante la variable unAnimal sólo es posible llamar a métodos definidos en la clase Animal, esta restricción la impone su tipo, que es "Animal", pero al momento de realizar la llamada al método "habla()" Gambas toma en consideración que el objeto al que apunta la variable unAnimal es en realidad de tipo Perro y que tiene su propia implementación del método "habla()". En consecuencia, ejecuta el método "habla()" de la clase "Perro".

A esta altura puede que se esté preguntando cuál es el beneficio. Lisa y llanamente el beneficio está en la flexibilidad que se gana. El objeto controlador tiene la posibilidad de manejar distintos tipos de objetos (gatos y perros) como si fueran uno sólo (Animal). Dicho de otro modo, el objeto que usa a los otros de modo polimórfico puede manipular diversas categorías de animales sin necesidad de que su código sea modificado. Así, resulta posible añadir tantas especies animales como se necesite sin necesidad de modificar siquiera una sola línea de código.

Más adelante veremos otros ejemplos de polimorfismo y compararemos una implementación de este programa que sigue el estilo modular y estructurado de programación con esta implementación basada en objetos que hace uso del polimorfismo, para que las ventajas sean más evidentes.



gravatar

Has usado "Super",(pero no lo explicas...) supongo que al poner "Super" hacer referencia a la clase "Superior" (la heredada) que es Animal, creo recordar que algo de eso venia en el libro de Gambas... pero (en mi opinicón) lo deberías comentar.

Se podría añadir un esquema como un conjunto (animal), que contiene a dos subconjuntos (perros y gatos). ¿o es al reves, el conjunto perro contiene animal, y el conjunto gato contiene animal...?, ya que estan heredando cosas de animal... ¿pueden tener propiedades mas propiedades el gato que de las que hereda del animal...Si ¿no?

Por otro lado que es "a" parece un contador...¿?, no entiendo el
for each a in animales
a.habla(a)
next

gravatar

Conceptualmente lo correcto es usar ME para acceder a los miembros heredados, ahora el texto lo refleja.

A nivel conceptual la clase descendiente incluye a la clase ancestra. Ello implica que la clase hija puede contener otras cosas.

"a" es una variable de tipo Animal.
El bucle FOR EACH recorre una clase enumerable, en este caso un array, y en cada iteración le asigna el siguiente elemento a la variable "a". FOR EACH permite iterar arrays y colecciones, no tiene relación directa con el polimorfismo.

Los comentarios están habilitados para que los lectores puedan participar en la corrección del libro, realizar preguntas puntuales o sugerencias. Todo comentario fuera de estos objetivos será eliminado. Por favor, tenga en cuenta lo siguiente:

- Cumpla las normas de etiqueta.

- Realice críticas constructivas.

- No sea redundante.