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:
- Agrega
abstracta la declaración de la clase - Cambia los atributos de
privateaprotected(las subclases los necesitan) - Declara
atacar()ycalcularDanio()como abstractos — sin cuerpo - Mantén
recibirDanio()ytoString()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:
- Extienda
Personaje - Implemente tanto
HechicerocomoSanador - Tenga al menos estos atributos propios:
mana: intpoderCuracion: int- Un atributo más de tu elección (ejemplos:
formaNatural,vinculoAnimal)
- Implemente todos los métodos abstractos y de interfaces:
atacar()— el druida ataca con fuerzas de la naturalezacalcularDanio()— a tu criterio (usa tus atributos)lanzarHechizo()— lanza un hechizo de naturalezagetMana()— retorna el manacurarAliado(Personaje aliado)— cura a otro personaje sumandopoderCuraciona su vidagetPoderCuracion()— retorna el poder de curación
💡 Pista para curarAliado(): Necesitas acceder a
puntosVidadel aliado. Como esprotectedenPersonaje, 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:
- Extienda
Personaje - Implemente solo
Hechicero - Tenga al menos 2 atributos propios
(ejemplos:
mana,almasAbsorbidas,nivelOscuridad,maldicion) - Implemente todos los métodos requeridos:
atacar()— el nigromante drena vida o invoca no-muertoscalcularDanio()— puede aumentar conalmasAbsorbidaslanzarHechizo()— lanza una maldición oscuragetMana()— 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:
- Extienda
Personaje - Implemente solo
Sanador - Tenga al menos 2 atributos propios
(ejemplos:
poderCuracion,instrumento,melodiasConocidas,carisma) - Implemente todos los métodos requeridos:
atacar()— el bardo aturde con su música o usa un instrumento como armacalcularDanio()— daño basado en sus atributoscurarAliado(Personaje aliado)— toca una melodía curativagetPoderCuracion()— retorna el poder de curación
Parte 7 — Clase principal Main
- Intenta crear un
new Personaje(...)y observa el error del compilador. Comenta esa línea. - Crea un objeto de cada tipo:
Druida,NigromanteyBardo. - Demuestra polimorfismo con un arreglo
Personaje[]:Personaje[] equipo = { druida, nigromante, bardo }; for (Personaje p : equipo) { p.atacar(); System.out.println("Daño: " + p.calcularDanio()); } - Usa
instanceofpara 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); } } - Demuestra curación: reduce la vida del
NigromanteconrecibirDanio()y luego cúralo con elDruiday elBardo.
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:
- ¿Por qué
Personajedebe ser abstracta y no concreta? ¿Qué problema resuelve? - ¿Qué ventaja tiene que
Druidaimplemente dos interfaces? ¿Podría extender dos clases en lugar de implementar dos interfaces? - Si agregas un nuevo personaje
Paladínque 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