Contexto

Una biblioteca necesita un catálogo mínimo para sus libros: título, autor, ISBN, cuántas copias hay disponibles para prestar y cuánto cuesta reponer un ejemplar perdido. A diferencia del ejercicio de la lección anterior, acá un Libro no puede nacer con datos basura: un título vacío o un precio de reposición negativo tienen que quedar reemplazados por un valor por defecto seguro en el mismo instante en que alguien intenta crear el objeto, no descubiertos después en algún reporte.

Consigna

Escribí una clase Java llamada Libro.java que represente un ejemplar del catálogo, y una clase MainBiblioteca.java con el método main que la ponga a prueba.

1. Atributos de la clase Libro

Definí los siguientes atributos, todos private (estado protegido, nunca expuesto directamente):

  • titulo (String, final)
  • autor (String, final)
  • isbn (String, final)
  • copiasDisponibles (int)
  • precioReposicion (double)

Estructura sugerida para arrancar (completá los cuerpos):

public class Libro {
    private final String titulo;
    private final String autor;
    private final String isbn;
    private int copiasDisponibles;
    private double precioReposicion;

    public Libro(String titulo, String autor, String isbn, int copiasDisponibles, double precioReposicion) {
        /* ... */
    }

    public Libro(String titulo, String autor, String isbn) {
        /* ... */
    }

    public boolean prestar() { /* ... */ }
    public void devolver() { /* ... */ }
    public boolean setPrecioReposicion(double precio) { /* ... */ }
    public void mostrarFicha() { /* ... */ }
}

2. El constructor canónico

Libro(String titulo, String autor, String isbn, int copiasDisponibles, double precioReposicion) es el único lugar donde vive la validación completa:

  • titulo, autor e isbn no pueden ser null ni estar en blanco.
  • copiasDisponibles no puede ser negativo.
  • precioReposicion tiene que ser mayor a 0.

Si alguna condición no se cumple, el constructor no corta la construcción: reemplaza el dato inválido por un valor por defecto seguro ("Sin título", "Autor desconocido", "ISBN pendiente", 0 copias o $15000.0) e informa por consola qué se rechazó y por qué. Ningún Libro termina de construirse con datos inválidos, pero tampoco deja de construirse: siempre gana un valor por defecto seguro.

3. El constructor de conveniencia y this(...)

Libro(String titulo, String autor, String isbn) es para el caso frecuente de un libro nuevo: arranca con 1 copia disponible y un precioReposicion por defecto de 15000.0. No repitas ninguna validación acá: delegá con this(...) en el constructor canónico, como primera sentencia del constructor, para que sea el único lugar donde la regla puede romperse o arreglarse.

Dejá comentado en main algo como // new Libro(); // no compila, con una línea que explique por qué: al declarar estos dos constructores propios, el constructor sin argumentos que el compilador regalaba automáticamente dejó de existir.

4. Getters y el setter validado

Escribí un getter público para cada uno de los cinco atributos. Para el precio de reposición, además, escribí setPrecioReposicion(double precio) como boolean, aplicando la misma regla que el constructor (mayor a 0): devuelve true si acepta el nuevo precio y false si lo rechaza, sin tocar el precio anterior. Que la regla viva en un solo lugar y el constructor la reutilice, o que ambos deleguen en un validador privado común: lo importante es que no esté escrita dos veces.

5. Operaciones de dominio en lugar de un setter de copias

No escribas setCopiasDisponibles(int). En su lugar, expresá la intención con dos métodos:

  • prestar() (boolean): si hay copias disponibles, descontá una, informá el préstamo y devolvé true. Si no hay copias, informá el error, no modifiques el atributo y devolvé false.
  • devolver(): suma una copia disponible e informa la devolución.

Un setter genérico de copias dejaría que cualquier código externo ponga el contador en cualquier valor, incluido uno negativo. prestar() y devolver() son la única puerta, y cada una aplica su propia regla.

6. MainBiblioteca: instanciación, validaciones y recorrido

En MainBiblioteca:

  • Creá al menos tres objetos Libro, combinando el constructor canónico y el de conveniencia.
  • Demostrá al menos dos rechazos usando los valores devueltos por los métodos: construí un libro con un dato inválido (por ejemplo, título vacío) y comprobá con getTitulo() que se usó el valor por defecto; y llamá a setPrecioReposicion con un valor inválido, comprobando que devuelve false y que getPrecioReposicion() no cambió.
  • Agotá las copias de algún libro con llamadas repetidas a prestar(), comprobando el boolean que devuelve cada llamada, y verificá que un préstamo de más informa el error, devuelve false y no deja el contador en negativo.
  • Llamá a mostrarFicha() de cada uno de los libros que creaste, cada uno en su propia variable (libro1, libro2, libro3). Guardar varios objetos en una sola estructura recorrible —un array de objetos o una colección— es el tema de una lección futura.

El formato exacto de tus mensajes puede variar, pero debe comunicar la misma información que la salida esperada de más abajo: qué se rechazó y por qué, la ficha de cada libro, y el resultado de prestar, agotar copias y devolver.