Módulo 12: Herencia
Objetivos del módulo
- Comprender el concepto de herencia
- Crear jerarquías de clases con
: - Usar
virtualyoverridepara modificar comportamientos - Entender
basepara acceder a la clase padre - Conocer clases
abstractysealed
1. ¿Qué es la herencia?
📘 Concepto: La herencia permite crear una clase nueva (clase hija o derivada) basada en una clase existente (clase padre o base). La clase hija hereda todas las propiedades y métodos de la padre, y puede añadir nuevos o modificar los existentes.
El problema sin herencia
// Sin herencia: hay código repetido en cada clase
class Perro { public string Nombre; public int Edad; public void Comer() { } public void Ladrar() { } }
class Gato { public string Nombre; public int Edad; public void Comer() { } public void Maullar() { } }
class Pez { public string Nombre; public int Edad; public void Comer() { } public void Nadar() { } }
// Nombre, Edad y Comer() están repetidos en las 3 clases
La solución con herencia
// Clase base: lo que tienen en común
class Animal
{
public string Nombre { get; set; } = "";
public int Edad { get; set; }
public void Comer()
{
Console.WriteLine($"{Nombre} está comiendo.");
}
}
// Clases derivadas: heredan todo y añaden lo específico
class Perro : Animal // ← Perro hereda de Animal
{
public string Raza { get; set; } = "";
public void Ladrar()
{
Console.WriteLine($"{Nombre} dice: ¡Guau!");
}
}
class Gato : Animal
{
public bool EsDeInterior { get; set; }
public void Maullar()
{
Console.WriteLine($"{Nombre} dice: ¡Miau!");
}
}
Uso
Perro perro = new Perro
{
Nombre = "Rex", // Heredado de Animal
Edad = 3, // Heredado de Animal
Raza = "Pastor alemán" // Propio de Perro
};
perro.Comer(); // Heredado de Animal: "Rex está comiendo."
perro.Ladrar(); // Propio de Perro: "Rex dice: ¡Guau!"
Gato gato = new Gato { Nombre = "Luna", Edad = 2, EsDeInterior = true };
gato.Comer(); // Heredado de Animal
gato.Maullar(); // Propio de Gato
2. virtual y override: modificar comportamiento
📘 Concepto: Si la clase padre marca un método como
virtual, las clases hijas pueden sobrescribirlo conoverridepara cambiar su comportamiento.
class Empleado
{
public string Nombre { get; set; } = "";
public double SalarioBase { get; set; }
// virtual: las clases hijas PUEDEN sobrescribir este método
public virtual double CalcularSalario()
{
return SalarioBase;
}
public override string ToString()
{
return $"{Nombre}: {CalcularSalario():C2}";
}
}
class EmpleadoConComision : Empleado
{
public double Ventas { get; set; }
public double PorcentajeComision { get; set; }
// override: sobrescribe el método del padre
public override double CalcularSalario()
{
return SalarioBase + (Ventas * PorcentajeComision / 100);
}
}
class Directivo : Empleado
{
public double BonoAnual { get; set; }
public override double CalcularSalario()
{
return SalarioBase + (BonoAnual / 12); // Bono prorrateado mensual
}
}
// Uso
var emp = new Empleado { Nombre = "Ana", SalarioBase = 2000 };
var com = new EmpleadoConComision
{
Nombre = "Luis",
SalarioBase = 1500,
Ventas = 50000,
PorcentajeComision = 5
};
var dir = new Directivo { Nombre = "María", SalarioBase = 4000, BonoAnual = 12000 };
Console.WriteLine(emp); // Ana: 2.000,00 €
Console.WriteLine(com); // Luis: 4.000,00 € (1500 + 50000*5%)
Console.WriteLine(dir); // María: 5.000,00 € (4000 + 12000/12)
3. base: acceder a la clase padre
La palabra base permite acceder a los miembros de la clase padre:
Llamar al constructor del padre
class Vehiculo
{
public string Marca { get; set; }
public string Modelo { get; set; }
public int Año { get; set; }
public Vehiculo(string marca, string modelo, int año)
{
Marca = marca;
Modelo = modelo;
Año = año;
}
}
class Coche : Vehiculo
{
public int Puertas { get; set; }
// Llamamos al constructor del padre con : base(...)
public Coche(string marca, string modelo, int año, int puertas)
: base(marca, modelo, año) // ← Pasar datos al constructor de Vehiculo
{
Puertas = puertas;
}
}
var coche = new Coche("Toyota", "Corolla", 2024, 4);
Llamar a un método del padre
class Animal
{
public virtual void HacerSonido()
{
Console.WriteLine("(sonido genérico)");
}
}
class Perro : Animal
{
public override void HacerSonido()
{
base.HacerSonido(); // Ejecuta el método del padre primero
Console.WriteLine("¡Guau guau!");
}
}
new Perro().HacerSonido();
// (sonido genérico)
// ¡Guau guau!
4. Clases abstractas (abstract)
📘 Concepto: Una clase
abstractno se puede instanciar (no puedes crear objetos de ella). Sirve como plantilla para clases hijas. Sus métodosabstractobligan a las hijas a implementarlos.
abstract class Figura
{
public string Color { get; set; } = "Negro";
// Método abstracto: NO tiene cuerpo, las hijas DEBEN implementarlo
public abstract double CalcularArea();
public abstract double CalcularPerimetro();
// Método normal: las hijas lo heredan tal cual
public void MostrarInfo()
{
Console.WriteLine($"Figura: {GetType().Name}");
Console.WriteLine($"Color: {Color}");
Console.WriteLine($"Área: {CalcularArea():F2}");
Console.WriteLine($"Perímetro: {CalcularPerimetro():F2}");
}
}
class Circulo : Figura
{
public double Radio { get; set; }
public Circulo(double radio) { Radio = radio; }
// OBLIGATORIO: implementar los métodos abstractos
public override double CalcularArea() => Math.PI * Radio * Radio;
public override double CalcularPerimetro() => 2 * Math.PI * Radio;
}
class Rectangulo : Figura
{
public double Ancho { get; set; }
public double Alto { get; set; }
public Rectangulo(double ancho, double alto) { Ancho = ancho; Alto = alto; }
public override double CalcularArea() => Ancho * Alto;
public override double CalcularPerimetro() => 2 * (Ancho + Alto);
}
class Triangulo : Figura
{
public double Base { get; set; }
public double Altura { get; set; }
public double Lado1 { get; set; }
public double Lado2 { get; set; }
public double Lado3 { get; set; }
public override double CalcularArea() => Base * Altura / 2;
public override double CalcularPerimetro() => Lado1 + Lado2 + Lado3;
}
// Uso
// Figura f = new Figura(); // ERROR: no se puede instanciar una clase abstracta
Figura circulo = new Circulo(5) { Color = "Rojo" };
Figura rect = new Rectangulo(4, 6) { Color = "Azul" };
circulo.MostrarInfo();
rect.MostrarInfo();
// Polimorfismo: una lista de diferentes figuras
List<Figura> figuras = new() { circulo, rect, new Triangulo
{
Base = 3, Altura = 4, Lado1 = 3, Lado2 = 4, Lado3 = 5
}};
double areaTotal = 0;
foreach (Figura f in figuras)
{
areaTotal += f.CalcularArea(); // Cada figura calcula su propia área
}
Console.WriteLine($"Área total: {areaTotal:F2}");
5. Clases selladas (sealed)
📘 Concepto: Una clase
sealedno puede ser heredada. Se usa cuando quieres evitar que alguien extienda tu clase.
sealed class ConfiguracionApp
{
public string NombreApp { get; set; } = "MiApp";
public string Version { get; set; } = "1.0";
// Singleton: una sola instancia
private static ConfiguracionApp? _instancia;
public static ConfiguracionApp Instancia => _instancia ??= new ConfiguracionApp();
private ConfiguracionApp() { } // Constructor privado
}
// class MiConfig : ConfiguracionApp { } // ERROR: no se puede heredar de sealed
var config = ConfiguracionApp.Instancia;
Console.WriteLine(config.NombreApp);
6. Operador is y casting
Puedes comprobar si un objeto es de cierto tipo y convertirlo:
Animal miAnimal = new Perro { Nombre = "Rex", Raza = "Labrador" };
// Comprobar tipo con 'is'
if (miAnimal is Perro perro)
{
Console.WriteLine($"Es un perro de raza {perro.Raza}");
}
// Casting explícito
if (miAnimal is Perro)
{
Perro p = (Perro)miAnimal; // Cast directo (lanza excepción si no es Perro)
}
// Casting con 'as' (devuelve null si no es compatible)
Gato? gato = miAnimal as Gato; // null porque miAnimal es Perro, no Gato
if (gato is not null)
{
Console.WriteLine($"Es un gato: {gato.Nombre}");
}
7. Herencia y ToString()
class Animal
{
public string Nombre { get; set; } = "";
public override string ToString() => $"{GetType().Name}: {Nombre}";
}
class Perro : Animal
{
public string Raza { get; set; } = "";
public override string ToString() => $"{base.ToString()}, Raza: {Raza}";
}
var p = new Perro { Nombre = "Rex", Raza = "Labrador" };
Console.WriteLine(p); // "Perro: Rex, Raza: Labrador"
8. Ejercicios
Ejercicio 1: Jerarquía de vehículos
Crea una jerarquía:
Vehiculo(base): marca, modelo, año, velocidad actual,Acelerar(),Frenar()Coche: puertas, tipo combustibleMoto: cilindradaCocheElectrico: nivel batería,Recargar()
Todos sobrescriben ToString(). Crea un garaje (lista de vehículos) y muestra todos.
Ejercicio 2: Figuras geométricas
Expande el ejemplo de figuras: añade Cuadrado (hereda de Rectangulo), Elipse, Trapecio. Calcula el área total de una lista de figuras mezcladas.
Ejercicio 3: Sistema de empleados
Clase base Empleado (nombre, ID, salario base). Subclases: EmpleadoFijo (+antigüedad, bono por año), EmpleadoTemporal (+fecha fin contrato), Directivo (+bono, empleados a cargo). Calcula la nómina total de la empresa.
Ejercicio 4: RPG con herencia
Crea un mini juego de rol: Personaje (base) con nombre, vida, ataque. Subclases: Guerrero (golpe fuerte, defensa alta), Mago (hechizo, maná), Arquero (flecha, precisión). Implementa combate por turnos entre dos personajes.
Resumen
| Concepto | Sintaxis |
|---|---|
| Herencia | class Hija : Padre { } |
| Método virtual | public virtual void Metodo() { } |
| Sobrescribir | public override void Metodo() { } |
| Llamar al padre | base.Metodo() |
| Constructor padre | : base(params) |
| Clase abstracta | abstract class Base { } |
| Método abstracto | public abstract void Metodo(); |
| Clase sellada | sealed class Final { } |
| Comprobar tipo | obj is Tipo variable |
| Casting | (Tipo)obj o obj as Tipo |