Laboratorio 06 — Clases Abstractas e Interfaces - jorgealbertopineda78-del/java GitHub Wiki

Laboratorio 06 — Clases Abstractas e Interfaces

Duración estimada: 90 minutos Nivel: Intermedio


Conexión con laboratorios anteriores

En los Labs 04 y 05 construiste una jerarquía de personajes con Personaje como clase base concreta. Funcionó bien, pero tiene un problema de diseño:

Personaje p = new Personaje("Desconocido", 1, 100);
p.atacar(); // ¿Qué hace exactamente un "Personaje genérico"?

Un personaje genérico no debería poder existir. En el mundo del juego solo existen clases específicas: Guerrero, Mago, Arquero... y los nuevos que agregarás hoy.

La solución es convertir Personaje en una clase abstracta. Nadie puede instanciarla directamente, pero sigue siendo la base de toda la jerarquía.


Objetivo

  • Definir y usar clases abstractas con métodos abstractos y concretos
  • Implementar interfaces para definir capacidades opcionales
  • Combinar clase abstracta + interfaz en una jerarquía coherente
  • Entender cuándo usar cada uno en un diseño real

Contexto del problema

El videojuego RPG recibe una expansión: tres nuevas clases de personaje se suman al roster. El equipo de diseño del juego define estas reglas:

  • Todo personaje tiene nombre, nivel y vida — pero no puede existir un "personaje genérico"
  • Todo personaje debe poder atacar y calcular su daño — pero cada clase lo hace diferente
  • Algunos personajes pueden usar magia — los que puedan deben implementar la interfaz Hechicero
  • Algunos personajes pueden curar aliados — los que puedan deben implementar la interfaz Sanador

Tu tarea es modelar esto con la arquitectura correcta.


Diagrama UML

          «interface»              «interface»
        ┌────────────┐           ┌────────────────┐
        │ Hechicero  │           │    Sanador     │
        ├────────────┤           ├────────────────┤
        │+lanzarHe-  │           │+curarAliado(   │
        │ chizo()    │           │  Personaje)    │
        │+getMana()  │           │+getPodCura()   │
        └─────┬──────┘           └───────┬────────┘
              │                          │
              └──────────┬───────────────┘
                         │ implements (según la clase)
                         ▼
          ┌──────────────────────────────┐
          │      «abstract»              │
          │        Personaje             │
          ├──────────────────────────────┤
          │ # nombre      : String       │
          │ # nivel       : int          │
          │ # puntosVida  : int          │
          │ # estaVivo    : boolean      │
          ├──────────────────────────────┤
          │ + Personaje(nombre,nivel,    │
          │             puntosVida)      │
          │ + getNombre()  : String      │
          │ + getNivel()   : int         │
          │ + getPuntosVida(): int       │
          │ + isEstaVivo() : boolean     │
          │ + recibirDanio(int): void    │
          │ + toString()   : String      │
          │ «abstract»                   │
          │ + atacar()     : void        │
          │ + calcularDanio(): int       │
          └──────────────┬───────────────┘
                         │  extends
          ┌──────────────┼──────────────┐
          ▼              ▼              ▼
      Druida          Nigromante      Bardo
   implements        implements    implements
   Hechicero         Hechicero      Sanador
   Sanador

Instrucciones

Parte 1 — Interfaz Hechicero (en clase con el profesor)

Crea Hechicero.java:

public interface Hechicero {
    void lanzarHechizo();
    int getMana();
}

Parte 2 — Interfaz Sanador (en clase con el profesor)

Crea Sanador.java:

public interface Sanador {
    void curarAliado(Personaje aliado);
    int getPoderCuracion();
}

Parte 3 — Clase abstracta Personaje (en clase con el profesor)

Convierte Personaje en una clase abstracta. Los cambios clave respecto al Lab04/05:

  1. Agrega abstract a la declaración de la clase
  2. Cambia los atributos de private a protected (las subclases los necesitan)
  3. Declara atacar() y calcularDanio() como abstractos — sin cuerpo
  4. Mantén recibirDanio() y toString() como métodos concretos — ya tienen implementación útil
public abstract class Personaje {

    protected String nombre;
    protected int nivel;
    protected int puntosVida;
    protected boolean estaVivo;

    public Personaje(String nombre, int nivel, int puntosVida) {
        this.nombre = nombre;
        this.nivel = nivel;
        this.puntosVida = puntosVida;
        this.estaVivo = true;
    }

    // Getters
    public String getNombre()   { return nombre; }
    public int getNivel()       { return nivel; }
    public int getPuntosVida()  { return puntosVida; }
    public boolean isEstaVivo() { return estaVivo; }

    // Método concreto — implementación compartida por todos
    public void recibirDanio(int danio) {
        puntosVida -= danio;
        if (puntosVida <= 0) {
            puntosVida = 0;
            estaVivo = false;
        }
        System.out.println(nombre + " recibe " + danio +
                           " de daño. Vida: " + puntosVida);
        if (!estaVivo) {
            System.out.println(nombre + " ha sido derrotado.");
        }
    }

    // Métodos abstractos — cada subclase DEBE implementarlos
    public abstract void atacar();
    public abstract int calcularDanio();

    @Override
    public String toString() {
        String vivo = estaVivo ? "Sí" : "No";
        return "Nombre: " + nombre + " | Nivel: " + nivel +
               " | Vida: " + puntosVida + " | Vivo: " + vivo;
    }
}

💡 Intenta crear un objeto: new Personaje("X", 1, 100) — ¿qué error te da el compilador? Ese error es exactamente el punto.


Parte 4 — Clase Druida (trabajo independiente)

El Druida es un personaje que domina la naturaleza. Puede lanzar hechizos y curar aliados.

Crea Druida.java que:

  1. Extienda Personaje
  2. Implemente tanto Hechicero como Sanador
  3. Tenga al menos estos atributos propios:
    • mana : int
    • poderCuracion : int
    • Un atributo más de tu elección (ejemplos: formaNatural, vinculoAnimal)
  4. Implemente todos los métodos abstractos y de interfaces:
    • atacar() — el druida ataca con fuerzas de la naturaleza
    • calcularDanio() — a tu criterio (usa tus atributos)
    • lanzarHechizo() — lanza un hechizo de naturaleza
    • getMana() — retorna el mana
    • curarAliado(Personaje aliado) — cura a otro personaje sumando poderCuracion a su vida
    • getPoderCuracion() — retorna el poder de curación

💡 Pista para curarAliado(): Necesitas acceder a puntosVida del aliado. Como es protected en Personaje, puedes accederlo directamente desde cualquier subclase en el mismo paquete.


Parte 5 — Clase Nigromante (trabajo independiente)

El Nigromante manipula la energía oscura. Solo puede lanzar hechizos, no curar.

Crea Nigromante.java que:

  1. Extienda Personaje
  2. Implemente solo Hechicero
  3. Tenga al menos 2 atributos propios (ejemplos: mana, almasAbsorbidas, nivelOscuridad, maldicion)
  4. Implemente todos los métodos requeridos:
    • atacar() — el nigromante drena vida o invoca no-muertos
    • calcularDanio() — puede aumentar con almasAbsorbidas
    • lanzarHechizo() — lanza una maldición oscura
    • getMana() — retorna el mana

Parte 6 — Clase Bardo (trabajo independiente)

El Bardo usa música y palabras para apoyar al equipo. No usa magia directa, pero puede curar.

Crea Bardo.java que:

  1. Extienda Personaje
  2. Implemente solo Sanador
  3. Tenga al menos 2 atributos propios (ejemplos: poderCuracion, instrumento, melodiasConocidas, carisma)
  4. Implemente todos los métodos requeridos:
    • atacar() — el bardo aturde con su música o usa un instrumento como arma
    • calcularDanio() — daño basado en sus atributos
    • curarAliado(Personaje aliado) — toca una melodía curativa
    • getPoderCuracion() — retorna el poder de curación

Parte 7 — Clase principal Main

  1. Intenta crear un new Personaje(...) y observa el error del compilador. Comenta esa línea.
  2. Crea un objeto de cada tipo: Druida, Nigromante y Bardo.
  3. Demuestra polimorfismo con un arreglo Personaje[]:
    Personaje[] equipo = { druida, nigromante, bardo };
    for (Personaje p : equipo) {
        p.atacar();
        System.out.println("Daño: " + p.calcularDanio());
    }
    
  4. Usa instanceof para identificar quién puede hacer qué:
    for (Personaje p : equipo) {
        if (p instanceof Hechicero h) {
            h.lanzarHechizo();
        }
        if (p instanceof Sanador s) {
            s.curarAliado(nigromante);
        }
    }
    
  5. Demuestra curación: reduce la vida del Nigromante con recibirDanio() y luego cúralo con el Druida y el Bardo.

La diferencia clave entre clase abstracta e interfaz

CLASE ABSTRACTA                    INTERFAZ
──────────────────────────────────────────────────────
Puede tener atributos              Solo constantes (static final)
Puede tener métodos concretos      Solo métodos abstractos*
Solo se extiende UNA               Se implementan MÚLTIPLES
Define lo que ES (identidad)       Define lo que PUEDE HACER
new ClaseAbstracta() → ERROR       new Interfaz() → ERROR
────────────────────────────────────────────────────────
* Java 8+ permite default methods en interfaces, tema avanzado

Ejemplo de salida esperada

=== RPG — Expansión: Nuevas Clases ===

-- Error esperado (línea comentada) --
// new Personaje(...) → cannot instantiate abstract class

-- Ataques y daño --
[Sylva] invoca raíces del bosque. Daño: 240
[Malachar] drena la esencia vital. Daño: 390
[Finnian] aturde con su laúd. Daño: 85

-- Solo los Hechiceros lanzan hechizos --
Sylva lanza: ¡Tormenta de espinas! (maná: 180)
Malachar lanza: ¡Maldición de decadencia! (maná: 220)

-- Solo los Sanadores curan --
Malachar recibe 300 de daño. Vida: 50
Sylva toca la tierra y cura a Malachar +120. Vida: 170
Finnian entona una melodía y cura a Malachar +60. Vida: 230

-- Estado final --
Nombre: Sylva    | Nivel: 7 | Vida: 200 | Vivo: Sí
Nombre: Malachar | Nivel: 6 | Vida: 230 | Vivo: Sí
Nombre: Finnian  | Nivel: 5 | Vida: 150 | Vivo: Sí

Criterios de evaluación

Criterio Puntos
Interfaz Hechicero declarada correctamente 5
Interfaz Sanador declarada correctamente 5
Clase abstracta Personaje con métodos abstractos y concretos 15
Druida extiende Personaje e implementa Hechicero + Sanador 20
Nigromante extiende Personaje e implementa Hechicero 15
Bardo extiende Personaje e implementa Sanador 15
curarAliado() modifica correctamente la vida del aliado 10
Main demuestra polimorfismo, instanceof y curación 10
Creatividad y coherencia en atributos y métodos propios 5
Total 100

Preguntas de reflexión

Responde en respuestas.docx:

  1. ¿Por qué Personaje debe ser abstracta y no concreta? ¿Qué problema resuelve?
  2. ¿Qué ventaja tiene que Druida implemente dos interfaces? ¿Podría extender dos clases en lugar de implementar dos interfaces?
  3. Si agregas un nuevo personaje Paladín que cura y combate físicamente, ¿qué clase extiende y qué interfaces implementa? ¿Tienes que modificar algo en las clases existentes?

Entregable

Sube al repositorio en la carpeta lab06_abstractas_interfaces:

lab06_abstractas_interfaces/
├── Hechicero.java
├── Sanador.java
├── Personaje.java
├── Druida.java
├── Nigromante.java
├── Bardo.java
└── Main.java

Laboratorio preparado para el curso de Programación Orientada a Objetos — Ago-Dic 2025