Como aprendimos en el capítulo sobre polimorfismo de subtipos, una operación definida para un supertipo puede sustituirse con seguridad por sus subtipos. Como resultado, las operaciones definidas para los objetos ‘Animal’ pueden aceptar con seguridad objetos ‘Gato’ o ‘Perro’.
Además, mencionamos que no debe confundir subtipado con herencia. La subtipificación describe relaciones entre tipos, mientras que la herencia se ocupa de las implementaciones. Esta distinción se vuelve clara con las interfaces.
Las interfaces son similares a las clases y métodos abstractos que acabamos de conocer, excepto que todos los «métodos» de una interfaz son abstractos. Una interfaz no contiene implementaciones.
Las interfaces especifican tipos, pero no especifican implementaciones. Tomemos una analogía del transporte. Tienes que ir de San Francisco a Los Ángeles. Es posible que no le importe cómo llegue allí, solo necesita un medio de transporte. Las interfaces no definen el «cómo», por lo que la interfaz en este caso sería el «modo de transporte». El «cómo» se implementaría en clases como ‘Tren’, ‘Coche’, ‘Avión’ o ‘Autobús’. Siempre que el «modo de transporte» pueda moverse, debe satisfacer sus necesidades. Sin embargo, no puede viajar en un «modo de transporte» abstracto; necesita un ‘Coche’, ‘Tren’, ‘Avión’ o ‘Autobús’.
Para visualizar estas relaciones:
interface IModeOfTransport { void move(); } class Car : IModeOfTransport { override void move() { // ... } } class Train : IModeOfTransport { override void move() { // ... } } class Airplane : IModeOfTransport { override void move() { // ... } } class Bus : IModeOfTransport { override void move() { // ... } }
Cada clase concreta se mueve de manera diferente. Por ejemplo, un ‘Coche’ tendrá una implementación muy diferente para el método ‘mover’ que un ‘Avión’ porque los aviones pueden volar.
A partir de estas relaciones, debería poder comprender que puede crear una instancia de una de las clases anteriores, como ‘Auto’, así:
Car rentalCar = new Car();
Supongamos que está desarrollando un sitio web de viajes. Desea proporcionar al usuario varias opciones para llegar de San Francisco a Los Ángeles. En este caso, es posible que desee generalizar el tipo:
IModeOfTransport transport = new Car();
Ahora, cuando el usuario selecciona una opción diferente (que no sea ‘Coche’), puede cambiar fácilmente el tipo de datos contenidos en la variable ‘transporte’ a una instancia de ‘Tren’, ‘Avión’ o ‘Autobús’.
Sin embargo, también reconoce que no puede ir de San Francisco a Los Ángeles por medio de un “modo de transporte”. Tienes que especificar qué tipo de medio de transporte. Por lo tanto, debe reconocer que esto resultará en un error del compilador:
IModeOfTransport transport = new IModeOfTransport();
Por lo tanto, usamos interfaces solo para especificar tipos y relaciones de tipos. Las interfaces no proporcionan implementaciones, por lo que no se pueden instanciar.
Equipados con este entendimiento, podemos cambiar nuestras relaciones de tipo ‘Animal’. Primero, comencemos con un ejemplo muy básico. Algunos de nuestros animales carecen de color. Es solo una página web en blanco y negro en este momento. Añadamos un poco de color.
Navegue a la carpeta ‘src/Animals/’ de su proyecto OOP que contiene Animal.jspp, Cat.jspp, Dog.jspp, etc. Dentro de esta carpeta, cree un nuevo archivo llamado IColorizable.jspp. Dentro de IColorizable.jspp, ingresemos este código:
module Animals { interface IColorizable { void colorize(); } }
Lo primero que notará es que anteponemos el nombre de nuestra interfaz con «I» («i» mayúscula). Esto se conoce como una convención de nomenclatura . Las convenciones de nomenclatura nos ayudan a nombrar clases, interfaces, módulos, funciones y variables que serán consistentes con los nombres usados por otros programadores que se unen a nuestro equipo, otras bibliotecas de terceros, etc. En JS ++, es típico prefijar nombres de interfaz con la letra «I» (mayúscula «i»).
Observe cómo no necesitamos el modificador ‘abstracto’. Todas las firmas de métodos dentro de una interfaz se consideran abstractas porque no se permiten implementaciones dentro de una interfaz.
A continuación, tenemos que especificar qué clases comparten una relación con IColorizable. Por ahora, démosle un poco de color solo a la clase ‘Perro’:
import Externals.DOM; external $; module Animals { class Dog : Animal, IColorizable { string _name; Dog(string name) { super("icofont-animal-dog"); _name = name; } final void render() { $element.attr("title", _name); super.render(); } final void talk() { alert("Woof!"); } final void colorize() { $element.css("color", "brown"); } } }
Ahora, tenemos que llamar al método colorize() en nuestra instancia ‘Dog’. Editar main.jspp:
import Animals; external $; IColorizable fido = new Dog("Fido"); fido.colorize(); Animal[] animals = [ new Cat("Kitty"), new Cat("Kat"), fido, new Panda(), new Rhino() ]; foreach(Animal animal in animals) { animal.render(); } $("#content").append("<p>Number of animals: " + Animal.getCount().toString() + "</p>");
Intenta compilar. Obtendrá un error:
[ ERROR ] JSPPE5034: Could not determine type for array literal. All elements in array literal must be the same, a mixture of primitive types, or descendants of the same base class at line 8 char 19 at main.jspp
La razón por la que esto sucede es porque la variable ‘fido’ es del tipo ‘IColorizable’. Mientras tanto, la array solo acepta elementos de tipo ‘Animal’. Si recuerda, nuestra clase ‘Perro’ hereda directamente de ‘IColorizable’ mientras que nuestra clase base ‘Animal’ no lo hace. Por lo tanto, no podemos insertar un objeto de ‘IColorizable’ directamente en una array de tipo ‘Animal’; de lo contrario, podríamos realizar operaciones no seguras como en JavaScript.
Observe, en el gráfico, que no existe una relación de tipo entre ‘Animal’ e ‘IColorizable’.
Hay varias formas de solucionar este problema. Lo solucionaremos haciendo que todos nuestros animales sean coloreables. Edite Dog.jspp y elimine la relación de tipos con ‘IColorizable’. Sin embargo, mantenga la implementación del método para ‘colorear’. Verás por qué más tarde. Aquí hay una visualización de lo que debe eliminar en Dog.jspp:
import Externals.DOM; external $; module Animals { class Dog : Animal, IColorizable{ string _name; Dog(string name) { super("icofont-animal-dog"); _name = name; } final void render() { $element.attr("title", _name); super.render(); } final void talk() { alert("Woof!"); } final void colorize() { $element.css("color", "brown"); } } }
Ahora abra Animal.jspp y agregue:
external $; module Animals { abstract class Animal : IColorizable { protected var $element; private static unsigned int count = 0; protected Animal(string iconClassName) { string elementHTML = makeElementHTML(iconClassName); $element = $(elementHTML); attachEvents(); Animal.count++; } public static unsigned int getCount() { return Animal.count; } public virtual void render() { $("#content").append($element); } public abstract void colorize(); public abstract void talk(); private void attachEvents() { $element.click(talk); } private string makeElementHTML(string iconClassName) { string result = '<div class="animal">'; result += '<i class="icofont ' + iconClassName + '"></i>'; result += "</div>"; return result; } } }
Dado que nuestra clase ‘Animal’ es ‘abstracta’, no necesitamos «implementar» el método ‘colorear’. En su lugar, simplemente lo posponemos un paso más allá de las clases derivadas de ‘Animal’ declarando el método ‘colorear’ como ‘abstracto’. ¿Recuerdas que no eliminamos el método ‘colorear’ de la clase ‘Perro’? Esta es la razón por. Hemos delegado la responsabilidad de implementar ‘colorear’ a las clases derivadas de ‘Animal’, pero aun así hemos podido describir una relación de tipo entre ‘Animal’ e ‘IColorizable’ donde ‘IColorizable’ es un supertipo de ‘Animal ‘.
Ahora, solo necesitamos agregar colores al resto de nuestros animales. Seré breve. Aquí hay una plantilla para el tipo de código que necesita agregar a ‘Cat.jspp’, ‘Panda.jspp’ y ‘Rhino.jspp’:
final void colorize() { $element.css("color", colorName); }
Reemplace ‘colorName’ con el color que desea hacer para el animal. Haz que tus gatos sean «dorados», los pandas «negros» y los rinocerontes «plateados».
Editar main.jspp:
import Animals; external $; Animal[] animals = [ new Cat("Kitty"), new Cat("Kat"), new Dog("Fido"), new Panda(), new Rhino() ]; foreach(Animal animal in animals) { animal.colorize(); animal.render(); } $("#content").append("<p>Number of animals: " + Animal.getCount().toString() + "</p>");
Compilar:
$ js++ src/ -o build/app.jspp.js
Abra index.html. El resultado debería verse así: