Polimorfismo
En resumen: El polimorfismo es cuando una llamada ejecuta la versión del método que pertenece al objeto real, no al tipo de la variable; la clase hija la sobrescribe.
Problema
El pago con un if para cada tipo
La empresa tiene empleados normales, gerentes con bono y becarios a media jornada. Alguien hizo una sola clase Employee con un campo «rol», y el pago se calcula así:
static int MonthlyPay(Employee e){ if (e.Role == "gerente") { return e.Salary + e.Bonus; } else if (e.Role == "becario") { return e.Salary / 2; } return e.Salary;}Funciona. Pero entonces llega un nuevo tipo de empleado: un contratista. Necesitas un nuevo else if aquí, y en el método de los impuestos, y en el método de la nómina, y en todos los lugares donde alguien preguntó por el rol. Y cada empleado lleva un campo de bono, aunque solo los gerentes reciben bono.
El código del gerente está repartido por todo el programa en lugar de estar en el gerente.
La idea
Cada clase decide por sí misma
Con la herencia de la lección anterior, Manager e Intern son clases separadas. Deja que cada una diga cuál es su pago mensual. La clase padre tiene una versión general, y la hija escribe su propia versión del mismo método:
class Employee{ // ... Name, Salary y el constructor
public virtual int MonthlyPay() { return Salary; }}
class Manager : Employee{ // ... Bonus y el constructor
public override int MonthlyPay() { return Salary + Bonus; }}Employee ivan = new Employee("Juan", 2000);Manager maria = new Manager("María", 3000, 500);Console.WriteLine(ivan.MonthlyPay());Console.WriteLine(maria.MonthlyPay());20003500Esto se llama sobrescribir un método. La hija escribe un método con el mismo nombre, los mismos parámetros y el mismo tipo de retorno. Para los objetos de la clase hija, su versión reemplaza a la de la clase padre.
En C# los dos lados tienen que estar de acuerdo. La clase padre dice que el método se puede sobrescribir, con virtual. La hija dice que lo sobrescribe, con override. Sin virtual en la clase padre, override no compila.
Más precisamente, para curiosos
- Si la hija escribe un método con el mismo nombre sin
override, el compilador avisa de que el método oculta al de la clase padre. Si de verdad quieres eso, escribesnew. Pero un método oculto no está sobrescrito: a través de una variable del tipo de la clase padre se llama a la versión de la clase padre.
class Report{ public virtual string Title() { return "Informe"; }}
class SalesReport : Report{ public new string Title() { return "Informe de ventas"; }}
SalesReport sales = new SalesReport();Report report = sales;Console.WriteLine(sales.Title());Console.WriteLine(report.Title());Informe de ventasInformesealed overridedice «las clases de más abajo no pueden volver a cambiarlo».- La hija no puede hacer el método más oculto: si es
publicen la clase padre, sigue siendopublic.
Pruébalo
¿Qué cambió?
El becario sobrescribe solo el método del pago mensual:
¿Qué imprimirá el programa?
class Intern : Employee{ public Intern(string name, int salary) : base(name, salary) { }
public override int MonthlyPay() { return Salary / 2; }}
Intern ana = new Intern("Ana", 1600);Console.WriteLine(ana.MonthlyPay());Console.WriteLine(ana.Salary);El MonthlyPay() del becario devuelve la mitad del salario. El salario en sí no cambia, y Salary se hereda de Employee tal cual.
La idea
Un array para todos
Un gerente es un empleado. Así que una variable de tipo Employee puede apuntar a un objeto de tipo Manager, y un array Employee[] puede guardar cualquier tipo de empleado:
Employee[] staff ={ new Employee("Juan", 2000), new Manager("María", 3000, 500), new Intern("Ana", 1600)};foreach (Employee e in staff){ Console.WriteLine(e.Name + ": " + e.MonthlyPay() + " €");}Juan: 2000 €María: 3500 €Ana: 800 €El bucle llama al mismo método, e.MonthlyPay(), y el resultado es distinto. La versión que se ejecuta es la del objeto, no la del tipo de la variable. Esto se llama polimorfismo: en griego, «muchas formas». Una llamada, y cada objeto responde a su manera.
Ahora un nuevo tipo de empleado es una clase nueva con su propio MonthlyPay(). El bucle y el resto del programa se quedan como están. No hay ningún if sobre el rol ni un campo de bono en los empleados que no reciben bono.
Pero el tipo de la variable decide qué puedes llamar. A través de e no llegas al bono, aunque el objeto sea un gerente: el compilador solo sabe que e es algún Employee. Cómo llegar a lo que solo tienen algunos objetos está en la lección «Comprobaciones de tipo».
Míralo
Qué método se ejecuta
Employee[] staff ={ new Employee("Juan", 2000), new Manager("María", 3000, 500), new Intern("Ana", 1600)};foreach (Employee e in staff){ Console.WriteLine(e.Name + ": " + e.MonthlyPay() + " €");}Paso a paso, en palabras
- El array es de tipo Employee, pero los objetos son de tres clases. Cada casilla guarda una flecha.
- e apunta a Juan, un Employee normal: se ejecuta el MonthlyPay de Employee.
- e apunta a María, un Manager: se ejecuta su propio método, salario más bono.
- e apunta a Ana, un Intern: el becario recibe la mitad del salario.
- La misma línea, tres métodos distintos. Decide el objeto, no el tipo de la variable.
El array guarda tres flechas de tipo Employee. Cada una apunta a un objeto de una clase distinta. En cada vuelta e apunta al objeto siguiente, y la llamada va al método de ese objeto: primero en Employee, después en Manager, después en Intern.
La elección se hace mientras el programa se ejecuta, no al compilar. El compilador no sabe qué habrá en el array. Solo comprueba que todo Employee tenga ese método.
Pruébalo
Encuentra el error
El programa es el mismo, pero el becario recibe el salario completo:
Juan: 2000 €María: 3500 €Ana: 1600 €Aquí está la clase Intern. ¿Qué línea tiene el error?
Elige la línea con el error.
override. Sin él, el método de Intern solo oculta al de Employee; no lo sobrescribe. Así que a través de una variable de tipo Employee se llama a la versión de la clase padre. El compilador avisa: «hides inherited member … To make the current member override that implementation, add the override keyword». La corrección es public override int MonthlyPay().La idea
Añade a la clase padre, no la reescribas
A veces la hija no quiere reemplazar el método por completo, solo añadirle algo. Un gerente se describe como un empleado, más el bono. Llamas a la versión de la clase padre con base.ToString():
public override string ToString(){ return base.ToString() + ", bono " + Bonus + " €";}Console.WriteLine(new Employee("Juan", 2000));Console.WriteLine(new Manager("María", 3000, 500));Juan (2000 €)María (3000 €), bono 500 €Si Employee cambia cómo se describe, el gerente lo sigue por sí solo.
Este es también el ToString() del módulo «Clases y objetos». Allí ya sobrescribías un método: la versión de object, que es virtual. Por eso escribías override.
Más precisamente, para curiosos
- Puedes llamar a
base.Method()desde cualquier método de la hija, no solo desde el que sobrescribe.
- La sobrescritura no es la sobrecarga del módulo «Métodos». En la sobrecarga el nombre es el mismo y los parámetros son distintos, y el compilador elige según los argumentos. En la sobrescritura todo es igual, y elige el objeto mientras el programa se ejecuta.
Pruébalo
¿Qué falta?
El becario se describe como un empleado más la palabra «becario». ¿Qué va en el hueco?
class Intern : Employee
{
public Intern(string name, int salary) : base(name, salary)
{
}
public override string ToString()
{
return .ToString() + ", becario";
}
}base.ToString() da «Ana (1600 €)», y la hija añade «, becario». Sin base. el método se llamaría a sí mismo, para siempre.
Ejercicio
Cada uno a su manera
Resuelve las tareas en tu editor. Cada tarea tiene un array del tipo padre y un solo bucle.
1. Refugio de animales. Una clase Animal con un nombre y un método MakeSound() que imprime «algún sonido». Dog y Cat lo sobrescriben. Pon uno de cada uno de los tres en un array Animal[]:
Invitado: algún sonidoRex: ¡Guau!Misi: ¡Miau, miau!2. El estudiante se presenta. Student de la lección anterior ya tiene una nota. Sobrescribe Introduce() para que también diga la nota. Usa base.Introduce(); no copies la línea de Person.
Soy Jorge, tengo 45 años.Soy Elena, tengo 19 años.Nota media: 5.53. Fiesta. Una clase Performance con un título y un método Start(). DanceMove, BreakDanceMove (hereda de DanceMove, añade complejidad) y Song lo sobrescriben. Una clase Party guarda un array Performance[] y tiene un método StartParty() que lo empieza todo en orden y al final dice que la fiesta ha empezado.
Bailamos moonwalkBailamos windmill complejidad 8 de 10Cantamos 'Bohemian Rhapsody'Empieza rifa¡La fiesta ha empezado!Más tareas
- Contratista. Añade a los empleados un
Contractor, con horas y una tarifa por hora. Su pago mensual es horas × tarifa. Calcula las nóminas de Juan, María, Ana y Pablo (60 horas a 25 €):
Total de nóminas: 7800 €- Nómina. Dale a
Employeeun métodoPayslip()que devuelve el texto «nombre: pago €» con el pago mensual. Escríbelo solo enEmployee. ¿Por qué funciona bien también para el gerente y para el becario?
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Constructores y this · Métodos de instancia · Encapsulamiento · Herencia
- Continúa en: Clases abstractas · Interfaces · Comprobaciones de tipo · Principios de la POO
- Ejercicios sobre el tema: Herencia y polimorfismo