Proyecto: aplicación orientada a objetos
Construiremos una biblioteca de consola. El objetivo no es acumular sintaxis, sino hacer que varias clases colaboren y protejan reglas claras.
Requisitos
La aplicación debe:
- registrar libros y revistas en un catálogo de capacidad fija;
- identificar cada material mediante un código;
- prestar y devolver solo los materiales prestables;
- impedir un préstamo repetido y una devolución imposible;
- listar materiales usando polimorfismo;
- separar modelo, servicio y punto de entrada en packages.
Usaremos arrays, ya conocidos en Java 1. No usaremos colecciones, ficheros, excepciones personalizadas, Maven ni tests automatizados.
Modelo y decisiones
Materiales abstracta: comparte código y título, pero cada subtipo describe sus datos.LibroyRevistason materiales: aquí la herencia representa «es un».Prestableexpresa una capacidad. Un libro se presta; una revista de consulta no tiene por qué hacerlo.Bibliotecatiene un array de materiales y coordina búsquedas.EstadoPrestamolimita los estados posibles.
Biblioteca → tiene → Material[]
Material ← Libro (también implementa Prestable)
Material ← Revista
Libro → usa → EstadoPrestamo
Estructura
src/
├── app/Main.java
├── modelo/EstadoPrestamo.java
├── modelo/Material.java
├── modelo/Libro.java
├── modelo/Revista.java
├── modelo/Prestable.java
└── servicio/Biblioteca.java
Implementación
src/modelo/EstadoPrestamo.java:
package modelo;
public enum EstadoPrestamo {
DISPONIBLE, PRESTADO
}
src/modelo/Prestable.java:
package modelo;
public interface Prestable {
boolean prestar();
boolean devolver();
}
src/modelo/Material.java:
package modelo;
public abstract class Material {
private String codigo;
private String titulo;
public Material(String codigo, String titulo) {
this.codigo = codigo;
this.titulo = titulo;
}
public String getCodigo() { return codigo; }
public String getTitulo() { return titulo; }
public abstract String descripcion();
}
src/modelo/Libro.java:
package modelo;
public class Libro extends Material implements Prestable {
private String autor;
private EstadoPrestamo estado;
public Libro(String codigo, String titulo, String autor) {
super(codigo, titulo);
this.autor = autor;
this.estado = EstadoPrestamo.DISPONIBLE;
}
@Override
public boolean prestar() {
if (estado != EstadoPrestamo.DISPONIBLE) return false;
estado = EstadoPrestamo.PRESTADO;
return true;
}
@Override
public boolean devolver() {
if (estado != EstadoPrestamo.PRESTADO) return false;
estado = EstadoPrestamo.DISPONIBLE;
return true;
}
@Override
public String descripcion() {
return "Libro " + getCodigo() + ": " + getTitulo()
+ " - " + autor + " - " + estado;
}
}
src/modelo/Revista.java:
package modelo;
public class Revista extends Material {
private int numero;
public Revista(String codigo, String titulo, int numero) {
super(codigo, titulo);
this.numero = numero;
}
@Override
public String descripcion() {
return "Revista " + getCodigo() + ": " + getTitulo()
+ " - número " + numero;
}
}
src/servicio/Biblioteca.java:
package servicio;
import modelo.Material;
import modelo.Prestable;
public class Biblioteca {
private Material[] catalogo;
private int cantidad;
public Biblioteca(int capacidad) {
catalogo = new Material[capacidad];
cantidad = 0;
}
public boolean registrar(Material material) {
if (material == null || cantidad == catalogo.length
|| buscar(material.getCodigo()) != null) {
return false;
}
catalogo[cantidad] = material;
cantidad++;
return true;
}
public Material buscar(String codigo) {
for (int i = 0; i < cantidad; i++) {
if (catalogo[i].getCodigo().equals(codigo)) return catalogo[i];
}
return null;
}
public boolean prestar(String codigo) {
Material material = buscar(codigo);
if (!(material instanceof Prestable)) return false;
return ((Prestable) material).prestar();
}
public boolean devolver(String codigo) {
Material material = buscar(codigo);
if (!(material instanceof Prestable)) return false;
return ((Prestable) material).devolver();
}
public void listar() {
for (int i = 0; i < cantidad; i++) {
System.out.println(catalogo[i].descripcion());
}
}
}
Aquí el instanceof responde a una necesidad concreta: el catálogo mezcla materiales y solo algunos cumplen Prestable. El cast se realiza después de comprobar el contrato.
src/app/Main.java:
package app;
import modelo.Libro;
import modelo.Revista;
import servicio.Biblioteca;
public class Main {
public static void main(String[] args) {
Biblioteca biblioteca = new Biblioteca(3);
System.out.println(biblioteca.registrar(
new Libro("L1", "Código limpio", "Ana Ruiz")));
System.out.println(biblioteca.registrar(
new Revista("R1", "Java Hoy", 12)));
System.out.println(biblioteca.registrar(
new Libro("L1", "Duplicado", "Otra autora")));
System.out.println(biblioteca.prestar("L1"));
System.out.println(biblioteca.prestar("L1"));
System.out.println(biblioteca.prestar("R1"));
biblioteca.listar();
System.out.println(biblioteca.devolver("L1"));
}
}
Compilar y ejecutar
Desde la raíz del proyecto:
mkdir out
javac -d out src/modelo/EstadoPrestamo.java src/modelo/Prestable.java \
src/modelo/Material.java src/modelo/Libro.java src/modelo/Revista.java \
src/servicio/Biblioteca.java src/app/Main.java
java -cp out app.Main
Casos manuales esperados
| Caso | Resultado |
|---|---|
registrar L1 | true |
registrar R1 | true |
repetir código L1 | false |
prestar L1 disponible | true |
volver a prestar L1 | false |
prestar revista R1 | false |
devolver L1 | true |
| buscar código inexistente | null |
Prueba también una biblioteca de capacidad cero y el registro de null: ambos deben devolver false sin romper el programa.
Revisión del diseño
La encapsulación impide cambiar el estado desde fuera. El enum evita estados inventados. La clase abstracta comparte identidad entre materiales, mientras la interfaz separa la capacidad de préstamo de la jerarquía. Biblioteca compone materiales y protege capacidad y códigos únicos.
No hemos creado Libro extends Biblioteca ni Revista extends Libro: esas relaciones serían falsas. Tampoco hemos incluido entrada por teclado en el modelo; Main es el límite de la aplicación.
Mini reto
Añade un Dvd prestable con duración en minutos.
Criterios: debe extender Material, implementar Prestable, proteger su estado y funcionar en Biblioteca sin modificar los recorridos.
Solución orientativa: sigue la estructura de Libro, sustituye autor por duración y sobrescribe descripcion(). Que el servicio no necesite cambios demuestra el valor del contrato y del polimorfismo.
Posibles ampliaciones
Más adelante podrían añadirse almacenamiento en ficheros, colecciones dinámicas, fechas de devolución, excepciones y pruebas automatizadas. No son requisitos ocultos de este proyecto: la versión actual queda completa y verificable con Java 2.