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. La función monthlyPay hace la nómina, y transfer envía el dinero al banco.

class Employee {
constructor(name, role, salary, bonus) {
this.name = name;
this.role = role;
this.salary = salary;
this.bonus = bonus;
}
}
function monthlyPay(e) {
if (e.role === "gerente") {
return e.salary + e.bonus;
} else if (e.role === "becario") {
return Math.trunc(e.salary / 2);
}
return e.salary;
}
function transfer(staff) {
let total = 0;
for (const e of staff) {
let amount = e.salary;
if (e.role === "gerente") {
amount += e.bonus;
}
console.log(`Transferencia: ${e.name} ${amount} €`);
total += amount;
}
console.log(`Total: ${total} €`);
}

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

const staff = [
new Employee("Juan", "empleado", 2000, 0),
new Employee("María", "gerente", 3000, 500),
new Employee("Ana", "becario", 1600, 0),
];
const percent = -10;
staff[0].salary += Math.trunc(staff[0].salary * percent / 100);
for (const e of staff) {
console.log(`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 privado, con #, y el aumento es un método que comprueba:

class Employee {
#name;
#salary;
constructor(name, salary) {
this.#name = name;
this.#salary = salary;
}
get name() {
return this.#name;
}
get salary() {
return this.#salary;
}
raise(percent) {
if (percent > 0) {
this.#salary += Math.trunc(this.#salary * percent / 100);
}
}
}
const ivan = new Employee("Juan", 2000);
ivan.raise(-10);
console.log(ivan.salary);
ivan.raise(10);
console.log(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.

Con # el campo es privado de verdad. Un programa que tiene esta línea fuera de la clase

ivan.#salary = -2000;

ni siquiera arranca:

Error
SyntaxError: Private field '#salary' must be declared in an enclosing class

Desde fuera el salario se lee a través del getter salary, que no tiene setter, y solo se cambia con raise().

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
  • # también protege de las clases hijas: Manager tampoco ve #salary. Por eso lee el salario a través del getter salary.
  • Antes de que existiera # (hasta 2022), JavaScript usaba el acuerdo _salary, como en Python. Lo verás en código más antiguo.

Pruébalo

¿Qué oculta el salario?

El salario tiene que verse solo dentro de la clase. ¿Cómo se llama el campo?
class Employee {
  #name;
  ;

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 extends Employee {
#bonus;
constructor(name, salary, bonus) {
super(name, salary);
this.#bonus = bonus;
}
get bonus() {
return this.#bonus;
}
}
const maria = new Manager("María", 3000, 500);
maria.raise(-10);
maria.raise(10);
console.log(`${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.

super(name, salary) pasa el nombre y el salario al constructor de la clase padre. #salary también está oculto para la clase hija, así que Manager lo lee a través del getter salary. Después de extends hay exactamente una clase.

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
monthlyPay() {
return this.#salary;
}
}
class Manager extends Employee {
// ... #bonus y el constructor
monthlyPay() {
return this.salary + this.#bonus;
}
}
class Intern extends Employee {
monthlyPay() {
return Math.trunc(this.salary / 2);
}
}

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

function transfer(staff) {
let total = 0;
for (const e of staff) {
console.log(`Transferencia: ${e.name} ${e.monthlyPay()} €`);
total += e.monthlyPay();
}
console.log(`Total: ${total} €`);
}
const staff = [
new Employee("Juan", 2000),
new Manager("María", 3000, 500),
new Intern("Ana", 1600),
];
for (const e of staff) {
console.log(`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.

JavaScript busca el método por su nombre: primero en la clase del objeto, después en su clase padre. No se comprueba nada por adelantado. Cualquier objeto que tenga un método con ese nombre sirve para el bucle, sea de la clase que sea. Esto se llama duck typing: «si camina como un pato y grazna como un pato, es un pato».

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 this.monthlyPay().

const e = new Intern("Ana", 1600);
e.raise(10);
console.log(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:

// El contrato «se puede pagar». Todo objeto pagable tiene:
// amountDue(): cuánto se debe
// payee(): a quién
class Employee {
// ... todo lo de antes
amountDue() {
return this.monthlyPay();
}
payee() {
return this.#name;
}
}
function transfer(payments) {
let total = 0;
for (const p of payments) {
console.log(`Transferencia: ${p.payee()} ${p.amountDue()} €`);
total += p.amountDue();
}
console.log(`Total: ${total} €`);
}
class Invoice {
#supplier;
#amount;
constructor(supplier, amount) {
this.#supplier = supplier;
this.#amount = amount;
}
amountDue() {
return this.#amount;
}
payee() {
return this.#supplier;
}
}
const 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.

JavaScript no tiene ni interface ni abstract. El contrato es un acuerdo sobre los nombres de los métodos, escrito en un comentario. Nadie lo comprueba: un objeto sin amountDue() entra en el array sin problema, y el programa se detiene solo cuando el bucle llega a él, con TypeError: p.amountDue is not a function. Ese es el precio del duck typing.

Un contrato es una forma de abstracción. La otra es una clase cuyos métodos detienen el programa si la clase hija no los escribe: 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. Un contrato 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.

const staff = [
new Employee("Juan", 2000),
new Manager("María", 3000, 500),
new Intern("Ana", 1600),
];
staff[0].raise(-10);
for (const e of staff) {
console.log(`Nómina: ${e.name} ${e.monthlyPay()} €`);
}
const 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 el contrato: amountDue() y payee(). 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
function monthlyPay(e) {
if (e.role === "gerente") {
return e.salary + e.bonus;
} else if (e.role === "becario") {
return Math.trunc(e.salary / 2);
}
return e.salary;
}
function transfer(staff) {
let total = 0;
for (const e of staff) {
let amount = e.salary;
if (e.role === "gerente") {
amount += e.bonus;
} else if (e.role === "becario") {
amount = Math.trunc(e.salary / 2);
}
console.log(`Transferencia: ${e.name} ${amount} €`);
total += amount;
}
console.log(`Total: ${total} €`);
}
const staff = [
new Employee("Juan", "empleado", 2000, 0),
new Employee("María", "gerente", 3000, 500),
new Employee("Ana", "becario", 1600, 0),
];
const percent = 10;
staff[0].salary += Math.trunc(staff[0].salary * percent / 100);
for (const e of staff) {
console.log(`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
function transfer(payments) {
let total = 0;
for (const p of payments) {
console.log(`Transferencia: ${p.payee()} ${p.amountDue()} €`);
total += p.amountDue();
}
console.log(`Total: ${total} €`);
}
const staff = [
new Employee("Juan", 2000),
new Manager("María", 3000, 500),
new Intern("Ana", 1600),
];
staff[0].raise(10);
for (const e of staff) {
console.log(`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:

const staff = [
new Employee("Juan", 2000),
new Manager("María", 3000, 500),
new Intern("Ana", 1600),
];
staff[0].raise(-10);
for (const e of staff) {
console.log(`Nómina: ${e.name} ${e.monthlyPay()} €`);
}
const 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
// El contrato «se puede pagar». Todo objeto pagable tiene:
// amountDue(): cuánto se debe
// payee(): a quién
class Employee {
#name;
#salary;
constructor(name, salary) {
this.#name = name;
this.#salary = salary;
}
get name() {
return this.#name;
}
get salary() {
return this.#salary;
}
raise(percent) {
if (percent > 0) {
this.#salary += Math.trunc(this.#salary * percent / 100);
}
}
monthlyPay() {
return this.#salary;
}
amountDue() {
return this.monthlyPay();
}
payee() {
return this.#name;
}
}
class Manager extends Employee {
#bonus;
constructor(name, salary, bonus) {
super(name, salary);
this.#bonus = bonus;
}
get bonus() {
return this.#bonus;
}
monthlyPay() {
return this.salary + this.#bonus;
}
}
class Intern extends Employee {
monthlyPay() {
return Math.trunc(this.salary / 2);
}
}
class Invoice {
#supplier;
#amount;
constructor(supplier, amount) {
this.#supplier = supplier;
this.#amount = amount;
}
amountDue() {
return this.#amount;
}
payee() {
return this.#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. super(name, salary);
  2. class Manager extends Employee {
  3. this.#bonus = bonus;
  4. constructor(name, salary, bonus) {
  5. }
  6. #bonus;
  7. }
  8. }
  9. monthlyPay() {
  10. return this.salary + this.#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 extends 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 {
#name;
#salary;
#account;
constructor(name, salary, account) {
this.#name = name;
this.#salary = salary;
this.#account = account;
}
payday() {
this.#account.deposit(this.#salary);
}
}
const account = new Account(100);
const ivan = new Employee("Juan", 2000, account);
ivan.payday();
console.log(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? En Java y C# a las dos a veces se las llama polimorfismo. La sobrecarga son varios métodos con un mismo nombre y parámetros distintos. JavaScript no la tiene: una segunda función con el mismo nombre reemplaza a la primera, y en su lugar hay valores por defecto (la lección sobre ellos). El polimorfismo en la POO es la sobrescritura: la elección la hace el objeto, mientras el programa se ejecuta.

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 {
total = 0;
}
// ... en 12 lugares del programa:
cart.total -= discount;

2.

function area(s) {
if (s instanceof Circle) return 3.14 * s.r * s.r;
if (s instanceof Rectangle) return s.w * s.h;
return 0;
}

3.

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

4.

// la única implementación:
class DefaultSalaryProvider {
provide(e) {
return e.monthlyPay();
}
}
const salaries = new DefaultSalaryProvider();
console.log(salaries.provide(maria));
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 #total, 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. Una clase 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:

function area(kind, a, 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. ¿Qué necesita un objeto para pasar por transfer()?

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