7.3. Funciones virtuales. La palabra override
Curso: Programación en C# (2015), por Nacho Cabanes
7.3. Funciones virtuales. La palabra override
En el ejemplo anterior hemos visto cómo crear un array de objetos, usando sólo la clase base en el momento de definirlo, pero insertando realmente objetos de cada una de las clases derivadas, y hemos visto que los constructores se llaman correctamente... pero con los métodos podemos encontrar problemas.
Vamos a verlo con un ejemplo que, en vez de tener constructores, va a tener un único método "Hablar", que se redefinirá en cada una de las clases hijas, y después comentaremos qué ocurre al ejecutarlo y cómo solucionarlo:
// Ejemplo_07_03a.cs // Array de objetos, sin "virtual" // Introducción a C#, por Nacho Cabanes using System; public class Ejemplo_07_03a { public static void Main() { // Primero creamos un animal de cada tipo Perro miPerro = new Perro(); Gato miGato = new Gato(); Animal miAnimal = new Animal(); miPerro.Hablar(); miGato.Hablar(); miAnimal.Hablar(); // Linea en blanco, por legibilidad Console.WriteLine(); // Ahora los creamos desde un array Animal[] misAnimales = new Animal[3]; misAnimales[0] = new Perro(); misAnimales[1] = new Gato(); misAnimales[2] = new Animal(); misAnimales[0].Hablar(); misAnimales[1].Hablar(); misAnimales[2].Hablar(); } } // ------------------ public class Animal { public void Hablar() { Console.WriteLine("Estoy comunicándome..."); } } // ------------------ public class Perro: Animal { public new void Hablar() { Console.WriteLine("Guau!"); } } // ------------------ public class Gato: Animal { public new void Hablar() { Console.WriteLine("Miauuu"); } }
(Recuerda que, como vimos en el apartado 6.4, ese "new" que aparece en "new void Hablar" sirve simplemente para anular un "warning" del compilador, que dice algo como "estás redifiniendo este método; añade la palabra new para indicar que eso es lo que deseas". Equivale a un "sí, sé que estoy redefiniendo este método").
La salida de este programa es:
Guau! Miauuu Estoy comunicándome... Estoy comunicándome... Estoy comunicándome... Estoy comunicándome...
La primera parte era de esperar: si creamos un perro, debería decir "Guau", un gato debería decir "Miau" y un animal genérico debería comunicarse. Eso es lo que se consigue con este fragmento:
Perro miPerro = new Perro(); Gato miGato = new Gato(); Animal miAnimal = new Animal(); miPerro.Hablar(); miGato.Hablar(); miAnimal.Hablar();
En cambio, si creamos un array de animales, no se comporta correctamente, a pesar de que después digamos que el primer elemento del array es un perro, el segundo es un gato y el tercero es un animal genérico:
Animal[] misAnimales = new Animal[3]; misAnimales[0] = new Perro(); misAnimales[1] = new Gato(); misAnimales[2] = new Animal(); misAnimales[0].Hablar(); misAnimales[1].Hablar(); misAnimales[2].Hablar();
Es decir, como la clase base es "Animal", y el array es de "animales", cada elemento hace lo que corresponde a un Animal genérico (dice "Estoy comunicándome"), a pesar de que hayamos indicado que se trata de un Perro u otra subclase distinta.
Generalmente, no será esto lo que queramos. Sería interesante no necesitar crear un array de perros y otros de gatos, sino poder crear un array de animales, y que pudiera contener distintos tipos de animales.
Para conseguir este comportamiento, debemos indicar a nuestro compilador que el método "Hablar" que se usa en la clase Animal quizá sea redefinido por otras clases hijas, y que, en ese caso, debe prevalecer lo que indiquen las clases hijas.
La forma de conseguirlo es declarar ese método "Hablar" como "virtual" (para indicar al compilador que ese método probablemente será redefinido en las subclases), y empleando en las subclases la palabra "override" en vez de "new" (para dejar claro que debe reemplazar al método "virtual" original), así:
// Ejemplo_07_03b.cs // Array de objetos, con "virtual" // Introducción a C#, por Nacho Cabanes using System; public class Ejemplo_07_03b { public static void Main() { // Primero creamos un animal de cada tipo Perro miPerro = new Perro(); Gato miGato = new Gato(); Animal miAnimal = new Animal(); miPerro.Hablar(); miGato.Hablar(); miAnimal.Hablar(); // Linea en blanco, por legibilidad Console.WriteLine(); // Ahora los creamos desde un array Animal[] misAnimales = new Animal[3]; misAnimales[0] = new Perro(); misAnimales[1] = new Gato(); misAnimales[2] = new Animal(); misAnimales[0].Hablar(); misAnimales[1].Hablar(); misAnimales[2].Hablar(); } } // ------------------ public class Animal { public virtual void Hablar() { Console.WriteLine("Estoy comunicándome..."); } } // ------------------ public class Perro: Animal { public override void Hablar() { Console.WriteLine("Guau!"); } } // ------------------ public class Gato: Animal { public override void Hablar() { Console.WriteLine("Miauuu"); } }
El resultado de este programa ya sí es el que posiblemente deseábamos: tenemos un array de animales, pero cada uno "Habla" como corresponde a su especie:
Guau! Miauuu Estoy comunicándome... Guau! Miauuu Estoy comunicándome...
Nota: Esto que nos acaba de ocurrir va a ser frecuente. Si el compilador nos avisa de que deberíamos añadir la palabra "new" porque estamos redefiniendo algo... es habitual que realmente lo que necesitemos sea "virtual" en la clase base y "override" en las clases derivada, especialmente si no se trata de un programa trivial, sino que vamos a tener objetos de varias clases coexistiendo en el programa, y especialmente si todos esos objetos van a ser parte de un único array o estructura similar (como las "listas", que veremos más adelante).
Ejercicios propuestos:
Ejercicio propuesto 7.3.1: Crea una versión ampliada del ejercicio 6.8.1 (clase Trabajador y relacionadas), en la que no se cree un único objeto de cada clase, sino un array de tres objetos.
Ejercicio propuesto 7.3.2: Amplía el proyecto Libro (ejercicio 6.7.2), de modo que permita guardar hasta 1.000 libros. Main mostrará un menú que permita añadir un nuevo libro o ver los datos de los ya existentes.
Ejercicio propuesto 7.3.3: Amplía el esqueleto del ConsoleInvaders (6.7.3), para que haya 10 enemigos en una misma fila (todos compartirán una misma coordenada Y, pero tendrán distinta coordenada X). Necesitarás un nuevo constructor en la clase Enemigo, que reciba los parámetros X e Y.
Ejercicio propuesto 7.3.4: A partir del ejemplo 07.02b y del ejercicio 6.8.1 (clase Trabajador y relacionadas), crea un array de trabajadores en el que no sean todos de la misma clase.
Ejercicio propuesto 7.3.5: Amplía el esqueleto de ConsoleInvaders (7.2.6) para que muestre las imágenes correctas de los enemigos, usando "virtual" y "override". Además, cada tipo de enemigos debe ser de un color distinto. (Nota: para cambiar colores puedes usar Console.ForegroundColor = ConsoleColor.Green;). La nave que maneja el usuario debe ser blanca.
Actualizado el: 29-01-2015 23:24