Herencia
En resumen: La herencia es cuando una clase (la hija) recibe los campos y los métodos de otra clase (la clase padre), así que la parte común se escribe en un solo lugar.
Problema
Dos copias del mismo código
El programa de nóminas tiene una clase Employee con un nombre, un salario y un método para un aumento. Después hizo falta también un gerente, que además recibe un bono. Lo más rápido fue copiar Employee en una clase nueva Manager y añadir el bono.
Un mes después alguien introduce un aumento del -10 % en lugar del 10 %. Así que añades una comprobación a Employee. Te olvidas de ella en Manager:
// en Employeepublic void Raise(int percent){ if (percent > 0) { Salary += Salary * percent / 100; }}
// en Manager: copia de antes de la comprobaciónpublic void Raise(int percent){ Salary += Salary * percent / 100;}Employee ivan = new Employee("Juan", 2000);Manager maria = new Manager("María", 3000, 500);ivan.Raise(-10);maria.Raise(-10);Console.WriteLine(ivan);Console.WriteLine(maria);Juan: 2000 €María: 2700 €El salario de Juan está protegido, pero el de María bajó 300 €. Las dos clases son iguales en un 90 %, y aun así cada corrección hay que hacerla dos veces.
La idea
La parte común va en la clase padre
Un gerente es un empleado, solo que con algo más. Dilo en el código: class Manager : Employee. En Manager escribes solo lo nuevo, el bono:
class Manager : Employee{ public int Bonus { get; private set; }
public Manager(string name, int salary, int bonus) : base(name, salary) { Bonus = bonus; }}
Manager maria = new Manager("María", 3000, 500);maria.Raise(-10);maria.Raise(10);Console.WriteLine(maria);Console.WriteLine("Bono: " + maria.Bonus + " €");María: 3300 €Bono: 500 €Ahora Manager tiene todo lo que tiene Employee: un nombre, un salario, Raise() con la comprobación, ToString(). Por eso el aumento del -10 % no pasó. La comprobación está escrita una vez y funciona para las dos clases.
Esto se llama herencia. Employee es la clase padre (clase base), y Manager es la clase hija. La hija lo toma todo de la clase padre y añade lo suyo.
Para comprobar si la herencia tiene sentido, usa la frase «X es un Y». «Un gerente es un empleado» es cierto. «Una factura es un empleado» no lo es, así que la factura no hereda de Employee, aunque tenga un importe y un nombre.
Pruébalo
¿Qué recibe el becario?
Intern no tiene nada propio salvo un constructor:¿Qué imprimirá el programa?
class Intern : Employee{ public Intern(string name, int salary) : base(name, salary) { }}
Intern ana = new Intern("Ana", 1200);ana.Raise(5);Console.WriteLine(ana);Raise() y ToString() se heredan de Employee. El 5 % de 1200 es 60, y 1200 + 60 = 1260.
La idea
Primero la clase padre
name y salary son private en Employee. La hija los tiene, pero no los ve: para la hija están ocultos, igual que para cualquier otra clase. Entonces, ¿cómo les da un valor Manager?
Llamando al constructor de la clase padre:
public Manager(string name, int salary, int bonus) : base(name, salary){ Bonus = bonus;}: base(name, salary) llama al constructor de Employee con estos argumentos. Prepara el nombre y el salario, y después el cuerpo del constructor de Manager prepara el bono. base se escribe después de los paréntesis del constructor, antes del cuerpo.
Hay un solo objeto. Contiene tanto la parte de la clase padre como la de la hija, y cada parte la prepara su propio constructor.
public Manager(string name, int salary, int bonus) : base(name, salary){ Bonus = bonus;}
Manager maria = new Manager("María", 3000, 500);Console.WriteLine(maria + ", bono " + maria.Bonus + " €");Paso a paso, en palabras
- new reserva un objeto. Contiene la parte de Employee y la parte de Manager.
- Empieza el constructor de Manager con los tres argumentos.
- Primero la clase padre: base(name, salary) llama al constructor de Employee, antes del cuerpo.
- El constructor de Employee prepara su parte: nombre y salario.
- Solo entonces el cuerpo de Manager prepara el bono.
- El objeto es uno solo, y cada parte la prepara su propio constructor.
Más precisamente, para curiosos
- Los constructores no se heredan.
Managerno tiene un constructorManager(string name, int salary)a menos que lo escribas. - Si la clase padre solo tiene un constructor con parámetros y la hija no le pasa nada, el programa no compila. El compilador no sabe qué nombre ni qué salario dar.
Pruébalo
¿Qué constructor se ejecuta primero?
Aquí la hija no llama a la clase padre de forma explícita:
¿Qué imprimirá el programa?
class Account{ public Account() { Console.WriteLine("Cuenta abierta"); }}
class SavingsAccount : Account{ public SavingsAccount() { Console.WriteLine("Interés: 3%"); }}
SavingsAccount savings = new SavingsAccount();: base(...), el compilador añade una llamada al constructor de la clase padre sin parámetros. La clase padre siempre va primero. Por eso se imprime primero «Cuenta abierta».Pruébalo
Encuentra el error
María debería tener un salario de 3000 € y un bono de 500 €. Pero el programa imprime:
María: 500 €¿En qué línea está el error?
Elige la línea con el error.
La clase padre recibe el bono, no el salario. Los argumentos se asignan a los parámetros por posición, así que Employee recibe 500 como salario. La corrección es : base(name, salary).
La idea
protected: solo para las hijas
A fin de año, el bono del gerente se suma al salario. El método está en Manager, pero el salario es private en Employee. Un getter no basta: hay que cambiarlo.
Entre private y public hay un tercer nivel de acceso: protected. La clase y sus hijas ven ese miembro, y todos los demás no:
class Employee{ public string Name { get; private set; } public int Salary { get; protected set; } // ... el resto, como antes}
class Manager : Employee{ // ... Bonus y el constructor
public void MergeBonus() { Salary += Bonus; Bonus = 0; }}Manager maria = new Manager("María", 3000, 500);maria.MergeBonus();Console.WriteLine(maria);Console.WriteLine("Bono: " + maria.Bonus + " €");María: 3500 €Bono: 0 €En C# es más habitual abrir no toda la propiedad, sino solo el setter: { get; protected set; }. Cualquiera puede leer el salario, pero solo Employee y sus hijas pueden cambiarlo.
protected debilita la protección: cualquier hija puede cambiar el salario sin pasar por la comprobación de Raise(). Por eso los campos siguen siendo private por defecto. protected es para los casos en los que la hija realmente tiene que tocar el campo.
Más precisamente, para curiosos
- En C#,
protectedes solo para la clase y sus hijas. También existeprotected internal: las hijas más todo lo del mismo proyecto.
Pruébalo
¿Qué falta?
Un becario es un empleado. ¿Qué va en el hueco?
class Intern Employee
{
public Intern(string name, int salary) : base(name, salary)
{
}
}class Intern : Employee. Los mismos dos puntos van también delante de base(...).La idea
Una clase padre y un antepasado común
Una clase tiene exactamente una clase padre. No puede heredar a la vez de Employee y de Student. Por eso la herencia es simple.
La cadena puede ser larga. Intern hereda de Employee. ¿Y de qué hereda Employee, si no tiene :? Toda clase sin padre hereda de object. Es el padre común de todas las clases. Por eso incluso una clase vacía tiene métodos:
class Ticket{}
Ticket a = new Ticket();Ticket b = new Ticket();Console.WriteLine(a.Equals(a));Console.WriteLine(a.Equals(b));Console.WriteLine(a.ToString());TrueFalseTicketEquals() viene de object. Por ahora solo te dice si dos variables apuntan al mismo objeto.
ToString() también viene de ahí. Si no lo escribes, devuelve el nombre de la clase, por eso arriba se imprime «Ticket». En el módulo «Clases y objetos» escribiste tu propio ToString() con override. Ahora sabes que así reemplazas la versión de object. Qué significa exactamente override está en la lección siguiente.
Más precisamente, para curiosos
- De una clase con
sealeddelante declassno se puede heredar.stringes una de ellas. objectes un nombre corto deSystem.Object. Una variable de tipoobjectpuede apuntar a cualquier objeto:object anything = maria;.
Ejercicio
Clases hijas
Resuelve las tareas en tu editor. Las clases padre son del módulo «Clases y objetos». Si no las tienes a mano, escríbelas otra vez: son cortas.
1. Coche familiar. SimpleCar tiene una velocidad y un método Accelerate() que la sube en 10. Escribe FamilyCar, que hereda de SimpleCar, empieza con 4 asientos y tiene un método AddSeat(). Crea un coche familiar, acelera y añade un asiento:
Velocidad: 10 km/hAsientos: 52. Estudiante. Person tiene un nombre, una edad e Introduce(). Escribe Student, que hereda de Person y añade una nota media Grade (un número decimal). Usa el constructor de la clase padre.
Soy Elena, tengo 19 años.Nota media: 5.53. Breakdance. DanceMove tiene un nombre, una duración en segundos y Perform(). Escribe BreakDanceMove con una complejidad del 1 al 10 y un método ShowComplexity().
Ejecuto moonwalk durante 5 segundosEjecuto windmill durante 12 segundoswindmill: complejidad 8 de 104. Coche eléctrico. Car tiene una marca, combustible en litros y los kilómetros recorridos. Refuel(liters) añade combustible, y Drive(distance) gasta 1 litro cada 10 km y suma los kilómetros (si no hay combustible suficiente, lo dice y no se mueve). Escribe ElectricCar:
- sin combustible, y con una batería que empieza al 100 %;
- con una eficiencia: cuántos km recorre con el 1 % de batería (5 para el «Tesla»);
Charge()carga la batería al 100 %;DriveElectric(distance)gasta batería en lugar de combustible y también suma los kilómetros.
private en Car. ¿Cómo los cambia la hija? Pista: la tarjeta sobre protected.Škoda: 150 km, combustible 35 lTesla: 200 km, batería 60%No alcanza la batería para 400 kmTesla: 600 km, batería 20%Más tareas
- Trabajador.
Workerhereda dePersony añade un puesto de trabajo y un métodoWork():
Soy Pablo, tengo 34 años.Trabajo como contable- Becario con mentor. Añade a
Internun campo con el nombre del mentor y pásalo en el constructor, junto con el nombre y el salario para la clase padre.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Clases y objetos · Constructores y this · Métodos de instancia · Encapsulamiento
- Continúa en: Polimorfismo · Clases abstractas · Interfaces · Comprobaciones de tipo
- Ejercicios sobre el tema: Herencia y polimorfismo