Tema 02

POO (Programación Orientada a Objetos) — Nivel principiante

Objetos, propiedades, métodos, clases y herencia… explicado para 2° cuatrimestre.

1) Fundamentos de POO: objetos, propiedades, métodos, clases e instancias

1.1 Recordatorio esencial: Array vs Objeto en JavaScript

En JavaScript, tanto los arreglos como los objetos son estructuras para almacenar información, pero se usan de forma distinta:

Punto clave para el curso:
• Un método es una función que pertenece a un objeto.
• Una propiedad es un dato que pertenece a un objeto.

1.2 Propiedad vs Método (y cómo identificarlo)

Un objeto puede tener dos tipos de miembros:

Regla práctica:
Si ves () → se está ejecutando una función → es un método.
Si no ves () → estás leyendo/escribiendo un valor → es una propiedad.
// Objeto literal (creado con {})
const persona = {
  nombre: "Ana",  // propiedad
  edad: 20,       // propiedad
  saludar: function () { // método
    return "Hola, soy " + this.nombre;
  }
};

// USO (leer propiedad)
console.log(persona.nombre); // "Ana"

// USO (ejecutar método)
console.log(persona.saludar()); // "Hola, soy Ana"

1.3 ¿Qué es Programación Orientada a Objetos (POO)?

POO es un enfoque para construir programas donde el sistema se modela con objetos. Cada objeto representa “algo” del mundo real o del sistema (persona, alumno, coche, personaje, cuenta bancaria, etc.) y se describe por:

En JavaScript puedes crear objetos directamente (como arriba), pero cuando necesitas crear muchos objetos “del mismo tipo”, se vuelve conveniente usar una clase.

1.4 Clase, objeto e instancia: definiciones precisas

Clase: es una plantilla (molde) que define cómo serán los objetos de ese tipo: qué propiedades tendrán y qué métodos podrán ejecutar.

Objeto: es el elemento creado a partir de una clase (el “resultado” del molde).

Instancia: es sinónimo de objeto cuando quieres enfatizar que ese objeto fue creado desde una clase. Por eso se dice: “instancia de Persona”.

Instanciar: significa “crear un objeto a partir de una clase”.

Conclusión rápida:
Clase = plantilla.
Instancia (objeto) = un elemento creado desde esa plantilla.

1.5 ¿Qué significa new?

La palabra new se usa para crear una nueva instancia de una clase. Cuando haces new Persona(...) ocurre lo siguiente:

  1. Se crea un objeto nuevo vacío.
  2. Se ejecuta el constructor de la clase.
  3. Se asignan propiedades usando this.
  4. Se regresa el objeto final.
class Persona {
  constructor(nombre, edad) {
    this.nombre = nombre;
    this.edad = edad;
  }

  presentarse() {
    return `Soy ${this.nombre} y tengo ${this.edad} años`;
  }
}

// INSTANCIAR = crear un objeto desde la clase (con new)
const p1 = new Persona("Ana", 20);
const p2 = new Persona("Luis", 22);

// USO: leer propiedades
console.log(p1.nombre); // "Ana"

// USO: ejecutar métodos
console.log(p2.presentarse()); // "Soy Luis y tengo 22 años"

1.6 ¿Qué es this?

this representa “este objeto” (la instancia actual). Dentro del constructor o de un método, this sirve para acceder a las propiedades del mismo objeto.

class Jugador {
  constructor(nombre) {
    this.nombre = nombre; // this.nombre pertenece a ESTE jugador
    this.hp = 100;
  }

  recibirDanio(cantidad) {
    this.hp = this.hp - cantidad; // modifica la vida de ESTE jugador
    return this.hp;
  }
}

const j1 = new Jugador("Davo");
console.log(j1.recibirDanio(25)); // 75
console.log(j1.hp);               // 75

1.7 ¿Por qué se escribe objeto.metodo() o objeto.propiedad?

El punto . es el operador de acceso a miembros. Se usa para entrar al interior de un objeto:

const p = new Persona("Ana", 20);

// propiedad: se lee sin ()
console.log(p.edad);

// método: se ejecuta con ()
console.log(p.presentarse());

1.8 Encapsulación (nivel básico)

A nivel principiante, encapsulación significa: el objeto tiene datos y métodos, y tú interactúas con el objeto principalmente a través de sus métodos. Eso ayuda a que el código sea más ordenado y fácil de mantener.

class Cuenta {
  constructor(saldoInicial) {
    this.saldo = saldoInicial;
  }

  depositar(monto) {
    this.saldo = this.saldo + monto;
    return this.saldo;
  }

  retirar(monto) {
    if (monto > this.saldo) return "Fondos insuficientes";
    this.saldo = this.saldo - monto;
    return this.saldo;
  }
}

const cuenta = new Cuenta(100);
console.log(cuenta.depositar(50)); // 150
console.log(cuenta.retirar(200));  // "Fondos insuficientes"

1.9 Herencia: extends y super() (explicación completa)

La herencia se usa cuando existe una relación del tipo: “X es un tipo de Y”. Por ejemplo: “Un mago es un tipo de personaje”.

¿Para qué sirve? Para reutilizar código. La clase hija hereda propiedades y métodos de la clase padre, y además puede agregar o modificar cosas.

class Personaje {
  constructor(nombre, vida) {
    this.nombre = nombre;
    this.vida = vida;
  }

  atacar() {
    return `${this.nombre} ataca`;
  }
}

class Mago extends Personaje {
  constructor(nombre, vida, mana) {
    super(nombre, vida); // 1) construye lo heredado (nombre y vida)
    this.mana = mana;    // 2) agrega lo propio del mago
  }

  lanzarHechizo() {
    if (this.mana <= 0) return `${this.nombre} no tiene maná`;
    this.mana = this.mana - 10;
    return `${this.nombre} lanza un hechizo (maná restante: ${this.mana})`;
  }
}

// Crear instancia del hijo
const m1 = new Mago("Gandor", 100, 30);

// USO: método heredado
console.log(m1.atacar()); // "Gandor ataca"

// USO: método del hijo
console.log(m1.lanzarHechizo()); // "Gandor lanza un hechizo ..."
Importante: Un objeto de la clase hija (ej. Mago) puede usar: métodos heredados (del padre) y métodos propios (del hijo).

2) Ejemplos guiados: cómo se crea y cómo se usa (propiedades y métodos)

2.1 Ejemplo A: Persona (objeto literal vs clase)

Primero, un objeto literal (se usa para un solo objeto rápido). Luego, una clase (se usa cuando crearás muchos).

// Objeto literal (1 solo)
const persona = {
  nombre: "Ana",
  edad: 20,
  presentarse: function () {
    return `Soy ${this.nombre} y tengo ${this.edad} años`;
  }
};

// USO
console.log(persona.nombre);        // leer propiedad
console.log(persona.presentarse()); // ejecutar método
// Clase (para crear varias personas)
class Persona {
  constructor(nombre, edad) {
    this.nombre = nombre;
    this.edad = edad;
  }
  presentarse() {
    return `Soy ${this.nombre} y tengo ${this.edad} años`;
  }
}

// INSTANCIAR (new)
const p1 = new Persona("Ana", 20);
const p2 = new Persona("Luis", 22);

// USO
console.log(p1.nombre);         // propiedad
console.log(p2.presentarse());  // método
Idea clave: Si vas a crear “muchos del mismo tipo”, conviene clase + new.

2.2 Ejemplo B: Coche (cambiar estado con métodos)

Este ejemplo muestra cómo un método puede cambiar una propiedad interna del objeto (estado).

class Coche {
  constructor(marca, modelo) {
    this.marca = marca;
    this.modelo = modelo;
    this.encendido = false;
  }

  encender() {
    this.encendido = true;
    return "Encendido";
  }

  apagar() {
    this.encendido = false;
    return "Apagado";
  }
}

const c1 = new Coche("Nissan", "Versa");

// USO: leer estado
console.log(c1.encendido); // false

// USO: ejecutar método que cambia estado
console.log(c1.encender()); // "Encendido"
console.log(c1.encendido);  // true

2.3 Ejemplo C: Jugador (parámetros en métodos)

Aquí se ve cómo un método recibe un parámetro y modifica propiedades del objeto.

class Jugador {
  constructor(nombre) {
    this.nombre = nombre;
    this.hp = 100;
  }

  recibirDanio(cantidad) {
    this.hp = this.hp - cantidad;
    if (this.hp < 0) this.hp = 0;
    return this.hp;
  }
}

const j1 = new Jugador("Davo");

// USO: llamar método con argumento
j1.recibirDanio(25);

// USO: leer propiedad ya modificada
console.log(j1.hp); // 75

2.4 Ejemplo D: Herencia (reutilizar código)

Se muestra un método heredado (padre) y uno propio (hijo). También se muestra super().

class Personaje {
  constructor(nombre, vida) {
    this.nombre = nombre;
    this.vida = vida;
  }

  atacar() {
    return `${this.nombre} ataca`;
  }
}

class Mago extends Personaje {
  constructor(nombre, vida, mana) {
    super(nombre, vida); // crea la parte heredada
    this.mana = mana;    // agrega lo propio
  }

  lanzarHechizo() {
    this.mana = this.mana - 10;
    return `${this.nombre} lanza un hechizo`;
  }
}

const m1 = new Mago("Gandor", 100, 50);

// USO: método heredado
console.log(m1.atacar());

// USO: método del hijo
console.log(m1.lanzarHechizo());
Interpretación correcta:
m1 es un objeto (instancia) de Mago.
• Como Mago hereda de Personaje, entonces m1 puede usar lo del padre y lo del hijo.

2.5 Ejemplo E: Array de objetos (relación con el examen anterior)

Esto mezcla ambos temas: arreglos (índice) + objetos (propiedades). Es exactamente el tipo de error común.

const alumnos = [
  { nombre: "Ana", edad: 20 },
  { nombre: "Luis", edad: 22 }
];

// Primero índice (porque es array), luego propiedad (porque es objeto)
const dato2 = alumnos[1].edad;

console.log(dato2); // 22

3) 5 ejercicios (para 1.5 hrs)

Hazlos en tu computadora. La idea es practicar clase, constructor, propiedades, métodos y extends.

Ejercicio 1

Crea una clase Persona con propiedades nombre y edad. Agrega un método presentarse() que regrese un string como: "Soy Ana y tengo 20 años".

class Persona {
  constructor(nombre, edad) {
    this.nombre = nombre;
    this.edad = edad;
  }
  presentarse() {
    return `Soy ${this.nombre} y tengo ${this.edad} años`;
  }
}

const p = new Persona("Ana", 20);
console.log(p.presentarse());

Ejercicio 2

Crea una clase Coche con propiedades marca, modelo y encendido (inicia en false). Métodos: encender() y apagar() que cambien encendido.

class Coche {
  constructor(marca, modelo) {
    this.marca = marca;
    this.modelo = modelo;
    this.encendido = false;
  }
  encender() { this.encendido = true; }
  apagar() { this.encendido = false; }
}

const c = new Coche("Nissan","Versa");
c.encender();
console.log(c.encendido);

Ejercicio 3

Crea una clase Jugador con nombre y hp (inicia en 100). Método recibirDanio(cantidad) que reste vida. No debe bajar de 0.

class Jugador {
  constructor(nombre) {
    this.nombre = nombre;
    this.hp = 100;
  }
  recibirDanio(cantidad) {
    this.hp = this.hp - cantidad;
    if (this.hp < 0) this.hp = 0;
    return this.hp;
  }
}

const j = new Jugador("Davo");
console.log(j.recibirDanio(25)); // 75

Ejercicio 4

Crea una clase Personaje con propiedades nombre y vida. Método atacar() que regrese un string. Luego crea una clase Mago que extienda Personaje y agregue propiedad mana y método lanzarHechizo().

class Personaje {
  constructor(nombre, vida) {
    this.nombre = nombre;
    this.vida = vida;
  }
  atacar() { return `${this.nombre} ataca`; }
}

class Mago extends Personaje {
  constructor(nombre, vida, mana) {
    super(nombre, vida);
    this.mana = mana;
  }
  lanzarHechizo() { return `${this.nombre} lanza un hechizo`; }
}

const m = new Mago("Gandor", 100, 50);
console.log(m.atacar());
console.log(m.lanzarHechizo());

Ejercicio 5

Crea un arreglo alumnos con 2 objetos como en el ejemplo (nombre, edad). Luego guarda en una variable dato2 la edad del segundo alumno usando alumnos[1].edad. Muestra el resultado en consola.

const alumnos = [
  { nombre: "Ana", edad: 20 },
  { nombre: "Luis", edad: 22 }
];

const dato2 = alumnos[1].edad;
console.log(dato2);

Ejercicio 6

Crea una clase Semaforo que controle un estado interno llamado color.

  • El constructor debe iniciar el color en "rojo".
  • Crea un método cambiar() que avance el color en este orden: rojo → amarillo → verde → rojo.
  • Crea un método estado() que regrese un texto como: "Semáforo en rojo".
  • Prueba tu clase: crea una instancia con new, imprime estado(), ejecuta cambiar() 3 veces y vuelve a imprimir estado() en cada paso.
class Semaforo {
  constructor() {
    this.color = "rojo";
  }

  cambiar() {
    if (this.color === "rojo") this.color = "amarillo";
    else if (this.color === "amarillo") this.color = "verde";
    else this.color = "rojo";
  }

  estado() {
    return `Semáforo en ${this.color}`;
  }
}

const s1 = new Semaforo();

console.log(s1.estado()); // Semáforo en rojo
s1.cambiar();
console.log(s1.estado()); // Semáforo en amarillo
s1.cambiar();
console.log(s1.estado()); // Semáforo en verde
s1.cambiar();
console.log(s1.estado()); // Semáforo en rojo

Ejercicio 7

Vamos a practicar herencia con un ejemplo distinto (sin personajes ni coches): un sistema de cuentas.

1) Crea una clase Cuenta con:

  • Propiedad titular
  • Propiedad saldo (inicia con el valor que reciba el constructor)
  • Método depositar(monto) que sume al saldo y lo regrese.
  • Método retirar(monto): si monto es mayor que el saldo, regresa "Fondos insuficientes"; si no, resta y regresa el nuevo saldo.

2) Crea una clase CuentaAhorro que extienda Cuenta y agregue:

  • Propiedad tasa (por ejemplo 0.05 = 5%)
  • Método aplicarInteres() que incremente el saldo así: saldo = saldo + (saldo * tasa) y regrese el nuevo saldo.
  • En el constructor de CuentaAhorro usa super(titular, saldo).

3) Prueba: crea una CuentaAhorro con saldo 1000 y tasa 0.05. Deposita 200, retira 500, aplica interés, e imprime el saldo final.

class Cuenta {
  constructor(titular, saldo) {
    this.titular = titular;
    this.saldo = saldo;
  }

  depositar(monto) {
    this.saldo = this.saldo + monto;
    return this.saldo;
  }

  retirar(monto) {
    if (monto > this.saldo) return "Fondos insuficientes";
    this.saldo = this.saldo - monto;
    return this.saldo;
  }
}

class CuentaAhorro extends Cuenta {
  constructor(titular, saldo, tasa) {
    super(titular, saldo);
    this.tasa = tasa;
  }

  aplicarInteres() {
    this.saldo = this.saldo + (this.saldo * this.tasa);
    return this.saldo;
  }
}

// PRUEBA
const ca = new CuentaAhorro("Ana", 1000, 0.05);

ca.depositar(200);           // 1200
ca.retirar(500);             // 700
ca.aplicarInteres();         // 735

console.log(ca.saldo);       // 735