Módulo 4·practica·6h
Objetivos de aprendizaje
- Usar getters y setters con validación.
- Diseñar jerarquías de clases realistas.
- Preferir composición sobre herencia cuando corresponde.
- Implementar el patrón toString() para debugging.
- Aplicar POO en un mini sistema de gestión.
POO en JavaScript (Parte II)
En esta segunda parte profundizamos en patrones de diseño con clases y desarrollamos un mini sistema completo que integra los cuatro pilares.
Getters y Setters con validación
Los getters y setters permiten ejecutar lógica al leer o escribir una propiedad:
javascript
class Temperatura {
#celsius;
constructor(celsius) {
this.celsius = celsius; // usa el setter
}
set celsius(valor) {
if (typeof valor !== "number") throw new TypeError("Debe ser un número");
if (valor < -273.15) throw new RangeError("Por debajo del cero absoluto");
this.#celsius = valor;
}
get celsius() { return this.#celsius; }
get fahrenheit() {
return (this.#celsius * 9) / 5 + 32;
}
get kelvin() {
return this.#celsius + 273.15;
}
toString() {
return `${this.#celsius}°C | ${this.fahrenheit}°F | ${this.kelvin}K`;
}
}
const t = new Temperatura(100);
console.log(t.toString()); // "100°C | 212°F | 373.15K"
t.celsius = -40;
console.log(t.fahrenheit); // -40 (punto de cruce C/F)
// t.celsius = -300; → RangeError: Por debajo del cero absolutoPatrón toString / valueOf
Sobreescribir toString() hace que los objetos sean autodescritos en logs y concatenaciones:
javascript
class Dinero {
#monto;
#moneda;
constructor(monto, moneda = "CLP") {
this.#monto = monto;
this.#moneda = moneda;
}
sumar(otro) {
if (this.#moneda !== otro.#moneda) throw new Error("Monedas distintas");
return new Dinero(this.#monto + otro.#monto, this.#moneda);
}
valueOf() { return this.#monto; } // permite comparar: dineroA > dineroB
toString() {
return new Intl.NumberFormat("es-CL", {
style: "currency",
currency: this.#moneda,
maximumFractionDigits: 0,
}).format(this.#monto);
}
}
const precio = new Dinero(25000);
const descuento = new Dinero(3000);
const final = precio.sumar(descuento);
console.log(`Total: ${final}`); // "Total: $28.000"
console.log(precio > descuento); // true (gracias a valueOf)Composición sobre herencia
"Prefiere composición sobre herencia" — principio clave de diseño.
La composición une objetos con roles específicos en lugar de crear jerarquías largas:
javascript
// Roles independientes y reutilizables
const Serializable = (Base) => class extends Base {
toJSON() {
return JSON.stringify(this, null, 2);
}
static fromJSON(json) {
return Object.assign(new this(), JSON.parse(json));
}
};
const Validable = (Base) => class extends Base {
validar() {
for (const [campo, valor] of Object.entries(this)) {
if (valor === null || valor === undefined || valor === "") {
return { valido: false, campo };
}
}
return { valido: true };
}
};
// Combinar roles con composición (mixin)
class Persona {
constructor(nombre, rut) {
this.nombre = nombre;
this.rut = rut;
}
}
class Empleado extends Validable(Serializable(Persona)) {
constructor(nombre, rut, cargo) {
super(nombre, rut);
this.cargo = cargo;
}
}
const emp = new Empleado("Ana García", "12.345.678-9", "Desarrolladora");
console.log(emp.validar()); // { valido: true }
console.log(emp.toJSON());
// {
// "nombre": "Ana García",
// "rut": "12.345.678-9",
// "cargo": "Desarrolladora"
// }Mini sistema: Gestor de Inventario
Un sistema real que integra todos los conceptos:
javascript
class Producto {
static #contador = 0;
#id;
#precio;
#stock;
constructor(nombre, precio, stock = 0) {
this.#id = ++Producto.#contador;
this.nombre = nombre;
this.#precio = precio;
this.#stock = stock;
}
get id() { return this.#id; }
get precio() { return this.#precio; }
get stock() { return this.#stock; }
set precio(valor) {
if (valor < 0) throw new RangeError("Precio no puede ser negativo");
this.#precio = valor;
}
agregarStock(cantidad) {
if (cantidad <= 0) throw new RangeError("Cantidad debe ser positiva");
this.#stock += cantidad;
return this; // permite encadenamiento
}
vender(cantidad) {
if (cantidad > this.#stock) throw new Error(`Stock insuficiente (${this.#stock} disponibles)`);
this.#stock -= cantidad;
return this;
}
toString() {
return `[${this.#id}] ${this.nombre} | $${this.#precio.toLocaleString("es-CL")} | Stock: ${this.#stock}`;
}
}
class Inventario {
#productos = new Map();
agregar(producto) {
this.#productos.set(producto.id, producto);
return this;
}
buscarPorNombre(texto) {
return [...this.#productos.values()].filter(p =>
p.nombre.toLowerCase().includes(texto.toLowerCase())
);
}
get total() {
return [...this.#productos.values()].reduce(
(suma, p) => suma + p.precio * p.stock, 0
);
}
listar() {
this.#productos.forEach(p => console.log(p.toString()));
}
}
// Uso
const inv = new Inventario();
inv
.agregar(new Producto("Laptop Dell", 800000, 5))
.agregar(new Producto("Monitor 27''", 350000, 10))
.agregar(new Producto("Teclado mecánico", 85000, 20));
inv.listar();
// [1] Laptop Dell | $800.000 | Stock: 5
// [2] Monitor 27'' | $350.000 | Stock: 10
// [3] Teclado mecánico | $85.000 | Stock: 20
console.log(`Valor total inventario: $${inv.total.toLocaleString("es-CL")}`);
// Buscar y vender
const laptops = inv.buscarPorNombre("laptop");
laptops[0].vender(2).agregarStock(3);
console.log(laptops[0].toString());
// [1] Laptop Dell | $800.000 | Stock: 6Práctica
Práctica interactiva · JavaScript
Esperado: ["El Quijote"]