Ir al contenido
wannacode

Principios de la POO

En resumen: La programación orientada a objetos se apoya en cuatro principios: encapsulamiento, herencia, polimorfismo y abstracción. Protegen los datos, escriben una sola vez lo común y sustituyen las cadenas de if sobre el tipo de objeto.

Problema

Nóminas sin principios

A final de mes la empresa paga a su gente. El programa se escribió con prisa: una sola clase con campos públicos, el rol es texto, y el pago se calcula en dos lugares. El método MonthlyPay hace la nómina, y Transfer envía el dinero al banco.

class Employee
{
public string Name;
public string Role;
public int Salary;
public int Bonus;
public Employee(string name, string role, int salary, int bonus)
{
Name = name;
Role = role;
Salary = salary;
Bonus = bonus;
}
}
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;
}
static void Transfer(Employee[] staff)
{
int total = 0;
foreach (Employee e in staff)
{
int amount = e.Salary;
if (e.Role == "gerente")
{
amount += e.Bonus;
}
Console.WriteLine("Transferencia: " + e.Name + " " + amount + " €");
total += amount;
}
Console.WriteLine("Total: " + total + " €");
}

Hoy contabilidad introduce un aumento para Juan. Pero en el formulario pone -10 en lugar de 10:

Employee[] staff =
{
new Employee("Juan", "empleado", 2000, 0),
new Employee("María", "gerente", 3000, 500),
new Employee("Ana", "becario", 1600, 0)
};
int percent = -10;
staff[0].Salary += staff[0].Salary * percent / 100;
foreach (Employee e in staff)
{
Console.WriteLine("Nómina: " + e.Name + " " + MonthlyPay(e) + " €");
}
Transfer(staff);
Salida
Nómina: Juan 1800 €
Nómina: María 3500 €
Nómina: Ana 800 €
Transferencia: Juan 1800 €
Transferencia: María 3500 €
Transferencia: Ana 1600 €
Total: 6900 €

El programa no se detiene y no se queja. Pero la salida tiene tres grietas:

  1. Una escritura incorrecta. El salario de Juan bajó a 1800 €. El campo está abierto a cualquier línea del programa, y nadie pregunta si -10% es un aumento.
  2. Dos copias de una regla. La nómina de Ana dice 800 €, y el banco le envía 1600 €. La regla «un becario cobra la mitad» está en la nómina, pero alguien se la olvidó en la transferencia. La copia se alejó del original, y nadie se dio cuenta.
  3. Un tipo nuevo, muchos arreglos. Mañana llega un trabajador autónomo. Necesitas un else if nuevo en la nómina, en la transferencia, en los impuestos, en las vacaciones, en todos los sitios donde alguien pregunta por el rol. Y cada empleado ya lleva un campo Bonus, aunque solo el gerente tiene bono.

La programación orientada a objetos es la respuesta justo a estas tres cosas.

La idea

Los cuatro principios

La programación orientada a objetos (POO) organiza un programa alrededor de objetos: datos junto con los métodos que los tocan. Se apoya en cuatro principios. Ya has trabajado con cada uno de ellos en los módulos «Clases y objetos» y «Herencia y polimorfismo»:

Principio Qué dice Qué grieta arregla
Encapsulamiento El objeto protege sus propios datos. Desde fuera llegas a ellos solo a través de métodos que comprueban. La escritura incorrecta: -10% se rechaza.
Herencia Lo que comparten varias clases se escribe una sola vez, en una clase padre. El bono vive solo en Manager, y lo común (el nombre, el salario, la comprobación del aumento) se escribe una sola vez en Employee.
Polimorfismo Una sola llamada, y cada objeto responde a su manera. El if sobre el rol: cada clase calcula su propio pago, sin segunda copia.
Abstracción El código depende de qué puede hacer un objeto, no de cómo lo hace. Un tipo de pago nuevo entra sin tocar la transferencia.

En resumen: el encapsulamiento protege, la herencia comparte, el polimorfismo elige, la abstracción promete.

A partir de aquí, cada principio arregla una grieta del programa de nóminas. Al final los verás juntos, en el mismo programa.

La idea

Encapsulamiento: el objeto protege sus datos

El salario pertenece al empleado. Que solo lo cambie el empleado. El campo pasa a ser private, y el aumento es un método que comprueba:

class Employee
{
private string name;
private int salary;
public Employee(string name, int salary)
{
this.name = name;
this.salary = salary;
}
public string Name
{
get { return name; }
}
public int Salary
{
get { return salary; }
}
public void Raise(int percent)
{
if (percent > 0)
{
salary += salary * percent / 100;
}
}
}
Employee ivan = new Employee("Juan", 2000);
ivan.Raise(-10);
Console.WriteLine(ivan.Salary);
ivan.Raise(10);
Console.WriteLine(ivan.Salary);
Salida
2000
2200

-10% se rechaza, 10% pasa. La comprobación está en un solo lugar, la clase, y todo código que quiera cambiar el salario pasa por ella. Esto es encapsulamiento: los datos y sus reglas están juntos en el objeto, y desde fuera solo ves lo que la clase decide mostrar.

Una línea que escribe directamente en el salario ya no compila:

ivan.salary = -2000;
Error de compilación
error CS0122: 'Employee.salary' is inaccessible due to its protection level

Desde fuera el salario se lee a través de la propiedad Salary, que solo tiene get, y solo se cambia con Raise(). Lo mismo se puede escribir más corto: public int Salary { get; private set; }, como en la lección «Encapsulamiento».

Cuándo duele: getters y setters para todo. Si cada campo tiene un getter y un setter que escribe lo que le des, el campo sigue abierto, solo que con más líneas. El encapsulamiento no es «un campo oculto más get y set». Es: el objeto decide qué puede cambiar y cómo. Por eso Employee no tiene ningún método que escriba el salario, solo un aumento con comprobación. Empieza con todo oculto y abre solo lo que haga falta.

Más precisamente, para curiosos
  • private protege el campo del código de fuera de la clase, no de la propia clase: cualquier método de Employee puede tocarlo. Por eso son los métodos de la propia clase los que cumplen la regla.
  • Un campo que no cambia después del constructor (el nombre) puede ser readonly, o una propiedad con solo get que se asigna en el constructor.

Pruébalo

¿Qué oculta el salario?

El salario tiene que verse solo dentro de la clase. ¿Qué va en el hueco?
class Employee
{
    private string name;
     int salary;

La idea

Herencia: lo común, una sola vez

Un gerente es un empleado con un bono. En lugar de una copia de Employee con un campo más, Manager lo hereda todo y solo añade el bono:

class Manager : Employee
{
private int bonus;
public Manager(string name, int salary, int bonus) : base(name, salary)
{
this.bonus = bonus;
}
public int Bonus
{
get { return bonus; }
}
}
Manager maria = new Manager("María", 3000, 500);
maria.Raise(-10);
maria.Raise(10);
Console.WriteLine(maria.Name + ": " + maria.Salary + " + " + maria.Bonus);
Salida
María: 3300 + 500

El gerente no tiene su propia comprobación del aumento. Toma la de Employee: -10% se rechaza, y 10% sube 3000 a 3300. Si mañana cambia la regla de los aumentos, la cambias en un solo lugar y vale para todas las clases hijas. Esto es herencia: lo común está en la clase padre, y la clase hija escribe solo su propia parte.

El salario es private en Employee, así que Manager tampoco lo ve directamente. Pasa el nombre y el salario al constructor de la clase padre con : base(name, salary), y después los lee como cualquier otro, a través de Salary. En Java y C# una clase tiene exactamente una clase padre.

La prueba de si la herencia tiene sentido es la frase «X es un Y». «Un gerente es un empleado» es cierto.

Cuándo duele: cadenas largas y heredar por el código ya hecho. Employee → Manager → SeniorManager → RegionalSeniorManager: para entender qué hace la clase de abajo, lees cuatro clases, y un cambio arriba mueve todo lo que hay debajo. Otro error común es heredar de una clase solo para tener un método ya hecho: una factura que extiende Employee para tener un nombre. «Una factura es un empleado» no es cierto, y recibiría un salario y un aumento. Qué hacer en su lugar está en la tarjeta «Qué se suele confundir», más abajo.

La idea

Polimorfismo: cada clase responde por sí misma

Ahora la grieta de las copias. En lugar de un if sobre el rol, cada clase calcula su propio pago mensual. La clase padre tiene una versión general, y las clases hijas la sobrescriben:

class Employee
{
// ... name, salary, Salary y Raise() de antes
public virtual int MonthlyPay()
{
return salary;
}
}
class Manager : Employee
{
// ... bonus y el constructor
public override int MonthlyPay()
{
return Salary + bonus;
}
}
class Intern : Employee
{
// ... el constructor
public override int MonthlyPay()
{
return Salary / 2;
}
}

Tanto la nómina como la transferencia llaman al mismo método:

static void Transfer(Employee[] staff)
{
int total = 0;
foreach (Employee e in staff)
{
Console.WriteLine("Transferencia: " + e.Name + " " + e.MonthlyPay() + " €");
total += e.MonthlyPay();
}
Console.WriteLine("Total: " + total + " €");
}
Employee[] staff =
{
new Employee("Juan", 2000),
new Manager("María", 3000, 500),
new Intern("Ana", 1600)
};
foreach (Employee e in staff)
{
Console.WriteLine("Nómina: " + e.Name + " " + e.MonthlyPay() + " €");
}
Transfer(staff);
Salida
Nómina: Juan 2000 €
Nómina: María 3500 €
Nómina: Ana 800 €
Transferencia: Juan 2000 €
Transferencia: María 3500 €
Transferencia: Ana 800 €
Total: 6300 €

Ana recibe 800 € tanto en la nómina como en la transferencia. La regla del becario vive solo en Intern, y no hay una segunda copia que se aleje. Un tipo nuevo de empleado es una clase nueva con su propio método, y el bucle y la transferencia quedan intactos. Esto es polimorfismo: una sola llamada, e.MonthlyPay(), y cada objeto responde a su manera.

En C# los dos lados se ponen de acuerdo. La clase padre dice con virtual que el método se puede sobrescribir, y la clase hija lo sobrescribe con override. Sin virtual, el método de la clase hija solo esconde el de la clase padre, y a través de una variable de tipo Employee se llama a la versión de la clase padre.

Cuándo duele. Dos casos que nunca llegarán a ser tres no necesitan una jerarquía de clases: un solo if es más corto y más claro. Además: una clase hija que sobrescribe el método pero rompe la promesa de la clase padre (un pago que sale negativo, o un método que detiene el programa) rompe todos los bucles que confían en la clase padre.

Pruébalo

¿Cuánto enviará el banco?

Las clases son de las tarjetas de arriba, junto con el contrato de pago de la tarjeta siguiente: AmountDue() está en Employee y devuelve MonthlyPay().

Employee e = new Intern("Ana", 1600);
e.Raise(10);
Console.WriteLine(e.AmountDue());
Respuesta

La idea

Abstracción: qué, no cómo

Contabilidad quiere las facturas de los proveedores en la misma transferencia. Una factura no es un empleado. Pero a la transferencia no le importa qué tipo de cosa es cada uno. Necesita dos cosas: a quién, y cuánto. Ese es el contrato:

interface IPayable
{
int AmountDue();
string Payee();
}
class Employee : IPayable
{
// ... todo lo de antes
public int AmountDue()
{
return MonthlyPay();
}
public string Payee()
{
return name;
}
}
static void Transfer(IPayable[] payments)
{
int total = 0;
foreach (IPayable p in payments)
{
Console.WriteLine("Transferencia: " + p.Payee() + " " + p.AmountDue() + " €");
total += p.AmountDue();
}
Console.WriteLine("Total: " + total + " €");
}
class Invoice : IPayable
{
private string supplier;
private int amount;
public Invoice(string supplier, int amount)
{
this.supplier = supplier;
this.amount = amount;
}
public int AmountDue()
{
return amount;
}
public string Payee()
{
return supplier;
}
}
IPayable[] payments =
{
new Employee("Juan", 2000),
new Manager("María", 3000, 500),
new Intern("Ana", 1600),
new Invoice("Compañía eléctrica", 340)
};
Transfer(payments);
Salida
Transferencia: Juan 2000 €
Transferencia: María 3500 €
Transferencia: Ana 800 €
Transferencia: Compañía eléctrica 340 €
Total: 6640 €

Transfer no sabe nada de salarios, bonos y facturas. Solo conoce el contrato. Esto es abstracción: muestras qué puede hacer un objeto y escondes cómo lo hace. La transferencia depende de la abstracción, así que todo lo que la cumple entra sin ningún cambio en la transferencia.

En C# el contrato es una interface, por costumbre con una I delante: IPayable. Employee e Invoice no tienen ninguna clase padre común aparte de object, pero las dos la implementan, y el compilador comprueba que los métodos están escritos. Manager e Intern reciben el contrato de Employee.

Un contrato es una forma de abstracción. La otra es la clase abstracta: Shape promete un área, y cada figura dice cómo se calcula («Clases abstractas»). Cuándo usar cada una está en la lección «Interfaces».

Cuándo duele: abstracción demasiado pronto. Una interfaz con una sola implementación, «por si acaso», es un nombre más, un archivo más y un salto más mientras lees. La abstracción compensa cuando hay al menos dos implementaciones, o cuando necesitas cambiar la que tienes, por ejemplo en un test. Payable apareció cuando llegó la factura, no antes.

Míralo

Los cuatro juntos

La escena recorre el programa desde el principio: cada principio arregla su propia grieta, y los números de abajo salen del mismo programa.

Employee[] staff =
{
new Employee("Juan", 2000),
new Manager("María", 3000, 500),
new Intern("Ana", 1600)
};
staff[0].Raise(-10);
foreach (Employee e in staff)
{
Console.WriteLine("Nómina: " + e.Name + " " + e.MonthlyPay() + " €");
}
IPayable[] payments = { staff[0], staff[1], staff[2], new Invoice("Compañía eléctrica", 340) };
Transfer(payments);
Paso a paso, en palabras
  1. Antes: el rol es texto, la regla del pago está copiada en dos cadenas de if, y cualquiera escribe el salario. Juan queda en 1800 €, y la nómina y la transferencia de Ana no coinciden.
  2. Encapsulamiento: el salario está oculto, y Raise() rechaza -10%. Juan se queda en 2000 €.
  3. Herencia: Manager toma de Employee el nombre, el salario y la comprobación del aumento, y solo añade el bono. Los objetos ya tienen clases, no un rol en texto.
  4. Polimorfismo: cada clase calcula su propio MonthlyPay(). La transferencia llama al mismo método que la nómina, y Ana recibe 800 € en las dos.
  5. Abstracción: la transferencia solo conoce la interfaz IPayable. La factura de la luz entra sin ser un empleado.
  6. Las cuatro juntas: el mismo -10% se rechaza, cada nómina coincide con su transferencia, y el total es 6640 €.

Aquí está el programa antes y después, con un 10% para Juan. El procedimiento «antes» se ha arreglado a mano: la regla del becario también se ha añadido a la transferencia.

Antes
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;
}
static void Transfer(Employee[] staff)
{
int total = 0;
foreach (Employee e in staff)
{
int amount = e.Salary;
if (e.Role == "gerente")
{
amount += e.Bonus;
}
else if (e.Role == "becario")
{
amount = e.Salary / 2;
}
Console.WriteLine("Transferencia: " + e.Name + " " + amount + " €");
total += amount;
}
Console.WriteLine("Total: " + total + " €");
}
Employee[] staff =
{
new Employee("Juan", "empleado", 2000, 0),
new Employee("María", "gerente", 3000, 500),
new Employee("Ana", "becario", 1600, 0)
};
int percent = 10;
staff[0].Salary += staff[0].Salary * percent / 100;
foreach (Employee e in staff)
{
Console.WriteLine("Nómina: " + e.Name + " " + MonthlyPay(e) + " €");
}
Transfer(staff);
Salida
Nómina: Juan 2200 €
Nómina: María 3500 €
Nómina: Ana 800 €
Transferencia: Juan 2200 €
Transferencia: María 3500 €
Transferencia: Ana 800 €
Total: 6500 €
Después
static void Transfer(IPayable[] payments)
{
int total = 0;
foreach (IPayable p in payments)
{
Console.WriteLine("Transferencia: " + p.Payee() + " " + p.AmountDue() + " €");
total += p.AmountDue();
}
Console.WriteLine("Total: " + total + " €");
}
Employee[] staff =
{
new Employee("Juan", 2000),
new Manager("María", 3000, 500),
new Intern("Ana", 1600)
};
staff[0].Raise(10);
foreach (Employee e in staff)
{
Console.WriteLine("Nómina: " + e.Name + " " + e.MonthlyPay() + " €");
}
Transfer(staff);
Salida
Nómina: Juan 2200 €
Nómina: María 3500 €
Nómina: Ana 800 €
Transferencia: Juan 2200 €
Transferencia: María 3500 €
Transferencia: Ana 800 €
Total: 6500 €

La salida es la misma. La diferencia está en lo que pase mañana:

  • Alguien introduce -10 → Raise() lo rechaza (encapsulamiento).
  • Una regla nueva para los aumentos → cambia en un solo lugar, Employee (herencia).
  • Un tipo nuevo de empleado → una clase nueva con su propio MonthlyPay(); la nómina y la transferencia quedan intactas (polimorfismo).
  • Una cosa nueva que pagar → una clase nueva que cumple el contrato; la transferencia queda intacta (abstracción).

El mismo programa con el -10% incorrecto del problema y con la factura:

Employee[] staff =
{
new Employee("Juan", 2000),
new Manager("María", 3000, 500),
new Intern("Ana", 1600)
};
staff[0].Raise(-10);
foreach (Employee e in staff)
{
Console.WriteLine("Nómina: " + e.Name + " " + e.MonthlyPay() + " €");
}
IPayable[] payments = { staff[0], staff[1], staff[2], new Invoice("Compañía eléctrica", 340) };
Transfer(payments);
Salida
Nómina: Juan 2000 €
Nómina: María 3500 €
Nómina: Ana 800 €
Transferencia: Juan 2000 €
Transferencia: María 3500 €
Transferencia: Ana 800 €
Transferencia: Compañía eléctrica 340 €
Total: 6640 €
Todas las clases en un solo lugar
interface IPayable
{
int AmountDue();
string Payee();
}
class Employee : IPayable
{
private string name;
private int salary;
public Employee(string name, int salary)
{
this.name = name;
this.salary = salary;
}
public string Name
{
get { return name; }
}
public int Salary
{
get { return salary; }
}
public void Raise(int percent)
{
if (percent > 0)
{
salary += salary * percent / 100;
}
}
public virtual int MonthlyPay()
{
return salary;
}
public int AmountDue()
{
return MonthlyPay();
}
public string Payee()
{
return name;
}
}
class Manager : Employee
{
private int bonus;
public Manager(string name, int salary, int bonus) : base(name, salary)
{
this.bonus = bonus;
}
public int Bonus
{
get { return bonus; }
}
public override int MonthlyPay()
{
return Salary + bonus;
}
}
class Intern : Employee
{
public Intern(string name, int salary) : base(name, salary)
{
}
public override int MonthlyPay()
{
return Salary / 2;
}
}
class Invoice : IPayable
{
private string supplier;
private int amount;
public Invoice(string supplier, int amount)
{
this.supplier = supplier;
this.amount = amount;
}
public int AmountDue()
{
return amount;
}
public string Payee()
{
return supplier;
}
}

Pruébalo

Ordena el gerente

Las líneas de Manager están desordenadas. Ponlas en el orden habitual: el campo, el constructor, y después el método del pago.

  1. {
  2. return Salary + bonus;
  3. {
  4. }
  5. public override int MonthlyPay()
  6. public Manager(string name, int salary, int bonus) : base(name, salary)
  7. this.bonus = bonus;
  8. {
  9. class Manager : Employee
  10. }
  11. }
  12. private int bonus;

Pruébalo

Encuentra el error

Un colega «simplificó» el contrato en Employee. El programa es el de «Los cuatro juntos», y imprime:

Salida
Nómina: Juan 2200 €
Nómina: María 3500 €
Nómina: Ana 800 €
Transferencia: Juan 2200 €
Transferencia: María 3000 €
Transferencia: Ana 1600 €
Total: 6800 €

La nómina de María es de 3500 €, y el banco le envía 3000 €. ¿En qué línea está el error?

Elige la línea con el error.

La idea

Qué se suele confundir

¿Abstracción o encapsulamiento? Los dos esconden algo, pero cosas distintas. El encapsulamiento esconde los datos de un objeto para que nadie de fuera los pueda romper: el salario está oculto, el aumento comprueba. La abstracción esconde las diferencias entre muchas clases detrás de una promesa común: la transferencia solo ve AmountDue() y Payee(). El encapsulamiento mira el objeto desde dentro, la abstracción mira desde fuera el código que lo usa.

¿Herencia o composición? La herencia dice «es un»: un gerente es un empleado. La composición dice «tiene un»: un empleado tiene una cuenta bancaria. Si haces al empleado clase hija de la cuenta de la lección «Encapsulamiento» para tener Deposit() gratis,

class Employee : Account
{
// ... name y salary
}

también recibirá los retiros, un saldo y todo lo que la cuenta aprenda mañana. Y «un empleado es una cuenta» no es cierto. En su lugar, la cuenta es un campo del empleado:

class Employee
{
private string name;
private int salary;
private Account account;
public Employee(string name, int salary, Account account)
{
this.name = name;
this.salary = salary;
this.account = account;
}
public void Payday()
{
account.Deposit(salary);
}
}
Account account = new Account(100);
Employee ivan = new Employee("Juan", 2000, account);
ivan.Payday();
Console.WriteLine(account.Balance);
Salida
2100

Por eso el consejo habitual es «prefiere la composición a la herencia». Hereda solo cuando «X es un Y» es cierto y lo seguirá siendo. Si no, deja que el objeto tenga al otro como campo.

¿Sobrecarga o sobrescritura? A las dos a veces se las llama polimorfismo. La sobrecarga son varios métodos con un mismo nombre y parámetros distintos: el compilador elige por los argumentos, antes de que arranque el programa. La sobrescritura es el mismo método en una clase hija: la elección la hace el objeto, mientras el programa se ejecuta. Cuando en la POO se dice «polimorfismo», casi siempre se habla de lo segundo.

Más precisamente, para curiosos
  • Algunos libros enumeran tres pilares: encapsulamiento, herencia y polimorfismo. La abstracción es entonces la idea común que está debajo. En una entrevista, lo que se suele esperar son cuatro.
  • Los principios no son reglas que se cumplen «al 100 %». Cada uno tiene un coste: los campos ocultos significan más métodos, las jerarquías más clases, los contratos un nombre más. Los usas donde la grieta es real.
  • Después de estos cuatro vienen los cinco principios SOLID para un código que aguanta los cambios, y los patrones de diseño: soluciones ya hechas construidas con los mismos cuatro. Están en el curso «Ingeniería de software»: SOLID y Patrones de diseño.

Ejercicio

¿Qué principio está roto?

Una pregunta clásica de entrevista. Para cada fragmento, responde en voz alta: qué principio está roto, qué se va a romper y cómo lo arreglas.

1.

class Cart
{
public double Total;
}
// ... en 12 lugares del programa:
cart.Total -= discount;

2.

static double Area(Shape s)
{
if (s is Circle c) return 3.14 * c.R * c.R;
if (s is Rectangle r) return r.W * r.H;
return 0;
}

3.

class Invoice : Employee
{
// recibe name y payee() ya hechos
}

4.

interface ISalaryProvider
{
int Provide(Employee e);
}
// la única implementación:
class DefaultSalaryProvider : ISalaryProvider
{
public int Provide(Employee e)
{
return e.MonthlyPay();
}
}
Las respuestas
  1. Encapsulamiento. El total está abierto y se reduce desde 12 lugares. Un descuento mayor que el total lo deja negativo, y nadie pregunta. El total pasa a ser private, y el descuento es un método del carrito que comprueba.
  2. Polimorfismo (y abstracción). Un if sobre el tipo de figura: una figura nueva significa una línea nueva aquí y en todos los sitios donde alguien pregunta por el tipo. El área pasa a ser un método abstracto de Shape, y cada figura escribe el suyo.
  3. Herencia en el lugar equivocado. «Una factura es un empleado» no es cierto: recibe un salario, un aumento y un pago mensual. Lo que comparten es que se les paga: un contrato Payable.
  4. Abstracción demasiado pronto. Un contrato con una sola implementación que solo pasa la llamada a MonthlyPay() es un nombre más y un salto más al leer, y no protege de nada. Quítalo y llama al método directamente. El contrato tendrá sentido cuando aparezca una segunda implementación.

Ejercicio

Un autónomo y una terraza

Resuélvelas en tu editor.

1. Un autónomo. Contractor (Pablo, 40 horas a 25 €) no es un empleado: no hay nómina, pero tiene que estar en la transferencia. Añádelo de manera que Transfer no cambie ni una línea. Pon también en la transferencia una factura de alquiler, 1800 € para el casero.

Salida
Transferencia: Juan 2000 €
Transferencia: María 3500 €
Transferencia: Ana 800 €
Transferencia: Pablo 1000 €
Transferencia: Casero 1800 €
Total: 9100 €

2. La terraza sin if. Este procedimiento calcula el área según el tipo de figura:

static int Area(string kind, int a, int b)
{
if (kind == "rectángulo") return a * b;
if (kind == "triángulo") return a * b / 2;
if (kind == "cuadrado") return a * a;
return 0;
}

Escríbelo con un Shape abstracto y las clases Rectangle, Triangle y Square. La terraza es un rectángulo de 4 × 3, un triángulo con base 2 y altura 3, y un cuadrado de lado 2. Un cuadrado es un rectángulo con los lados iguales: escríbelo como clase hija de Rectangle, sin fórmula de área propia.

Salida
Rectángulo: 12 m²
Triángulo: 3 m²
Cuadrado: 4 m²
Total: 19 m²

Más tareas

  • Una nómina también para el autónomo. Si al final el autónomo quiere una nómina, sin pasar a ser un empleado: ¿qué tiene que estar en el contrato, y quién lo cumple?
  • Pagar en una cuenta. Dale al empleado una cuenta (composición, como en «Qué se suele confundir») y un método que, el día de pago, ingrese MonthlyPay(), no el salario. El gerente también tiene que recibir el bono, sin tocar MonthlyPay() en Manager.
  • Un aumento para todos. Sube el salario de cada empleado un 5% en un solo bucle. ¿Qué clases tocas?

Test

Ponte a prueba

1. El salario está oculto, y solo cambia a través de Raise(), que rechaza un porcentaje menor que cero. ¿Qué principio es este?
2. La nómina y la transferencia calculan el pago con dos copias del if sobre el rol, y una de ellas se olvidó del becario. ¿Qué principio elimina la segunda copia?
3. ¿En qué se diferencian la abstracción y el encapsulamiento?
4. Una factura necesita un nombre y un importe. Un colega propone que Invoice extienda Employee para tenerlos gratis. ¿Por qué no?
5. ¿Cuál de los dos es el polimorfismo en el que decide el objeto, mientras el programa se ejecuta?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·