Como mencionamos en la sección anterior, si queremos polimorfismo en tiempo de ejecución, el uso de conversiones puede conducir a un código sucio.
A modo de ejemplo, cambiemos nuestro código main.jspp para que todos nuestros animales estén dentro de una array. A partir de ahí, recorreremos la array para renderizar el animal. Abra main.jspp y cambie el código a:
import Animals; Animal[] animals = [ new Cat("Kitty"), new Cat("Kat"), new Dog("Fido"), new Panda(), new Rhino() ]; foreach(Animal animal in animals) { if (animal instanceof Cat) { ((Cat) animal).render(); } else if (animal instanceof Dog) { ((Dog) animal).render(); } else { animal.render(); } }
Ahora nuestro código es aún menos elegante que nuestro código original que solo instanciaba los animales, especificaba el tipo más específico y llamaba a render().
Sin embargo, este código se puede simplificar enormemente hasta que se vuelva elegante. De hecho, podemos reducir el ciclo ‘foreach’ a una declaración. La respuesta: métodos virtuales.
Los métodos virtuales permiten el «enlace tardío». En otras palabras, el método específico para llamar se resuelve en tiempo de ejecución en lugar de en tiempo de compilación. No necesitamos todas las comprobaciones de ‘instancia de’, todas las conversiones y todas las declaraciones ‘si’ como vimos en el código anterior. Podemos lograr algo mucho más elegante.
Primero, abra Animal.jspp y cambie el método ‘renderizar’ para incluir el modificador ‘virtual’:
external $; module Animals { class Animal { protected var $element; protected Animal(string iconClassName) { string elementHTML = makeElementHTML(iconClassName); $element = $(elementHTML); } public virtual void render() { $("#content").append($element); } private string makeElementHTML(string iconClassName) { string result = '<div class="animal">'; result += '<i class="icofont ' + iconClassName + '"></i>'; result += "</div>"; return result; } } }
Guarde Animal.jspp. Ese es el único cambio que necesitamos hacer.
Sin embargo, hacer que nuestro método sea virtual no es suficiente. En Cat.jspp y Dog.jspp, estamos usando el modificador ‘overwrite’ en sus métodos de ‘render’. El modificador ‘overwrite’ especifica la resolución en tiempo de compilación. Queremos resolución en tiempo de ejecución. Todo lo que tenemos que hacer es cambiar Cat.jspp y Dog.jspp para usar el modificador ‘override’ en lugar del modificador ‘overwrite’. En aras de la brevedad, solo mostraré el cambio a Cat.jspp, pero también debe realizar el cambio a Dog.jspp:
external $; module Animals { class Cat : Animal { string _name; Cat(string name) { super("icofont-animal-cat"); _name = name; } override void render() { $element.attr("title", _name); super.render(); } } }
Eso es todo. Todo lo que teníamos que hacer era cambiar los modificadores. Ahora finalmente podemos editar main.jspp para que solo haya una declaración dentro del bucle:
import Animals; Animal[] animals = [ new Cat("Kitty"), new Cat("Kat"), new Dog("Fido"), new Panda(), new Rhino() ]; foreach(Animal animal in animals) { animal.render(); }
Compile su código y abra index.html. Todo debería funcionar. Ahora hemos podido simplificar enormemente nuestro código y aun así obtener el comportamiento esperado. Específicamente, redujimos el código de nuestro bucle ‘foreach’ de:
foreach(Animal animal in animals) { if (animal instanceof Cat) { ((Cat) animal).render(); } else if (animal instanceof Dog) { ((Dog) animal).render(); } else { animal.render(); } }
A esto:
foreach(Animal animal in animals) { animal.render(); }
La razón por la que hemos podido simplificar nuestro código de manera tan drástica es porque marcar un método como ‘virtual’ significa un potencial polimorfismo en tiempo de ejecución. Junto con el modificador ‘override’, el compilador sabe que queremos un enlace tardío en el método ‘render’, por lo que el enlace «tardío» ocurre exactamente cuando es necesario: el método ‘render’ se resolverá en tiempo de ejecución si y solo cuando sea necesario. ser resuelto (dentro del bucle ‘foreach’).