El polimorfismo es uno de los principios fundamentales de la programación orientada a objetos (POO), que permite que los objetos puedan ser tratados como instancias de su clase base, mientras que conservan su propio comportamiento específico.
En C# esto se logra a través de:
- Métodos virtuales
- Métodos abstractos
- Interfaces
Si quieres aprender más sobre programación orientada a objetos, de dejo el enlace al Curso de Programación orientada a objetos.
Métodos virtuales y sobreescritura
Un método virtual es aquel que se declara en una clase base y que puede ser sobrescrito en una clase derivada para modificar o extender su comportamiento.
En la clase base, el método se marca con la palabra clave virtual
, mientras que en la clase derivada se utiliza override
para especificar que ese método ha sido modificado.
public class Animal
{
public virtual void HacerSonido()
{
Console.WriteLine("El animal hace un sonido.");
}
}
public class Perro : Animal
{
public override void HacerSonido()
{
Console.WriteLine("El perro ladra.");
}
}
public class Gato : Animal
{
public override void HacerSonido()
{
Console.WriteLine("El gato maúlla.");
}
}
En el ejemplo anterior, HacerSonido
es un método virtual en la clase base Animal
.
Tanto Perro
como Gato
sobrescriben este método para especificar un sonido diferente, logrando así que cada subclase defina su comportamiento específico.
Animal miAnimal = new Perro();
miAnimal.HacerSonido(); // Salida: El perro ladra.
miAnimal = new Gato();
miAnimal.HacerSonido(); // Salida: El gato maúlla.
A pesar de que la variable miAnimal
es del tipo Animal
, ejecuta el método sobrescrito en la clase específica (Perro
o Gato
).
Esto es el polimorfismo en acción: permite que el método HacerSonido
se comporte de acuerdo con el tipo concreto del objeto en tiempo de ejecución.
Ocultación de métodos
En C# existe otra forma de redefinir el comportamiento de un método heredado usando la palabra clave new
. Este enfoque se denomina ocultación de métodos y permite que una clase derivada defina una nueva versión de un método de la clase base.
Al utilizar new
en lugar de override
, el método de la clase derivada oculta el de la clase base. Esto significa que cuando se llama al método desde una referencia de tipo de clase base, el método original se ejecuta, y no el de la clase derivada.
public class Animal
{
public virtual void HacerSonido()
{
Console.WriteLine("El animal hace un sonido.");
}
}
public class Loro : Animal
{
public new void HacerSonido()
{
Console.WriteLine("El loro imita sonidos.");
}
}
En este ejemplo, la clase Loro
define una nueva versión de HacerSonido
utilizando new
, en lugar de override
. Esto hace que el método de Loro
esté presente solo cuando el objeto es accedido directamente como tipo Loro
.
La palabra clave new
es útil cuando se necesita definir un método en una subclase con el mismo nombre que el de la clase base, pero que no debe modificar su comportamiento polimórfico.
Aunque new
es menos común que override
, puede ser una buena elección si se quiere dar un comportamiento alternativo sin cambiar la estructura de la jerarquía de clases base.
Polimorfismo con interfaces
Otra forma de implementar el polimorfismo en C# es mediante el uso de interfaces. Las interfaces definen contratos que las clases deben implementar, permitiendo trabajar con diferentes clases que implementen la misma interfaz de una manera uniforme.
public interface ISonido
{
void HacerSonido();
}
public class Perro : ISonido
{
public void HacerSonido()
{
Console.WriteLine("El perro ladra.");
}
}
public class Gato : ISonido
{
public void HacerSonido()
{
Console.WriteLine("El gato maúlla.");
}
}
En este caso, las clases Perro
y Gato
implementan la interfaz ISonido
, lo que significa que ambas deben definir el método HacerSonido
. Esto permite tratar a Perro
y Gato
de forma polimórfica.
ISonido miSonido = new Perro();
miSonido.HacerSonido(); // Salida: El perro ladra.
miSonido = new Gato();
miSonido.HacerSonido(); // Salida: El gato maúlla.
El uso de interfaces facilita el manejo de diferentes tipos de objetos de una manera estandarizada y extensible. Podemos introducir nuevas clases que implementen ISonido
sin alterar el código que utiliza esta interfaz, permitiendo una gran flexibilidad.