Pildorita de POO y Java: Gestor Universitario III

Hoy terminamos con nuestro Gestor Universitario, primer ejemplo de aplicación Java que afrontamos en estas pildoritas.

En las dos primeras partes desarrollamos el modelo y la capa de lógica (que llamamos manager). Hoy veremos la capa Vista, es decir, la interfaz de usuario.

Aunque seguramente no lo sabéis aún, esta organización de nuestras clases sigue un patrón de arquitectura muy conocido llamado Modelo Vista Controlador (MVC), el cuál separa los datos y la lógica de una aplicación, de la interfaz de usuario.

Como ya adelantamos en la anterior pildorita, vamos a utilizar las librerías de Java Standard I/O para realizar la interfaz, aunque más adelante podremos cambiarla por otras realizadas con JavaFX o HTML. Es una de las bondades de utilizar una arquitectura MVC, que los componentes de cada capa pueden ser intercambiables sin que se vea afectada el resto de capas de la aplicación.

Al igual que hicimos con las clases Manager, vamos a crear una clase Vista para cada Entidad, así como una general que tenga un menú principal a través del cual se pueda acceder a los submenús de cada entidad. Estas clases de vista lo que harán es mostrar las opciones y datos almacenados, así como recoger la información necesaria para ejecutar las funcionalidades que se encuentran implementadas en las clases de la capa Manager.

Si pensáis de forma más detenida sobre esta organización, veréis que se van a dar ciertas problemáticas que tienen que ver con los conceptos de Clase y Objeto.

Por ejemplo, pensemos en la vista de Asignaura. Esta clase invocará fundamentalmente métodos existentes en la clase AsignaturaManager, pero también utilizaremos el método de búsqueda de Profesores contenidos en ProfesorManager. Por tanto, necesitaremos declarar dentro de esta Vista ambos Manager. Asimismo, el profesor también tendrá que tener una instancia del ProfesorManager para todas las funcionalidades de dicha entidad.

¿Veis ya el problema? Vamos a tener que declarar dos objetos ProfesorManager, uno en la Vista de Profesores y otro en el de las Asignaturas. Si creamos un objeto de la manera que conocemos hasta ahora, con un constructor, tendremos dos objetos diferentes, con estados diferentes. Por tanto, la Lista de Asignaturas y la Lista de Profesores serían diferentes en cada Vista. ¿Cómo podemos asegurarnos de tener una sola instancia para toda la aplicación?

La forma más sencilla y que de seguro se os ha ocurrido ya, sería instanciar una sola vez cada manager en esa clase de vista principal, y luego crear las subvistas de cada entidad pasándoles como parámetro en sus constructores los managers que necesiten. Como veréis ahora, es lo que hacemos con la clase Scanner. Con este enfoque tendríamos la seguridad de que todas las vistas apuntan a las mismas instancia de cada Manager, ya que cuando pasamos como parámetro una Clase compleja por parámetro, estamos pasando realmente un puntero al Objeto de esa clase.

No obstante, esta forma, aunque sencilla y conocida ya por todos, obligaría a cambiar nuestro constructor o como poco a crear clases de seteo para los Managers, cada vez que añadiéramos nuevos menús con nuevos Managers.

La segunda opción – que vamos a utilizar en este ejemplo para adentrarnos un poco en otro tema que veremos más adelante – es basarnos en un patrón de diseño llamado Singleton. Este patrón nos permite restringir que una clase sólo tenga una instancia global, proporcionando además un único punto de acceso a ella.

Puede sonar muy complejo, pero realmente sólo significa hacer lo siguiente:

  • Crear un método privado de la clase para asegurarnos de que no pueda ser instanciada desde fuera
  • Crear una clase estática de la propia clase como atributo privado
  • La creacion de un método estático, getInstance, que nos devuelva la instancia única de la clase, creando el objeto también en caso de que aún no se haya hecho anteriormente
public class ProfesorManager {
	private List<Profesor> lsProfesores = new ArrayList<Profesor>();

	private static ProfesorManager profesorManager;
	
	private ProfesorManager() {
		super();
	}
	
	public static ProfesorManager getInstance() {
		if (profesorManager == null)
			profesorManager = new ProfesorManager();
		return profesorManager;
	}
}

Por otra parte, para acceder a los métodos de ProfesorManager lo haríamos siempre pasando por el método getInstance().

ProfesorManager.getInstance().getProfesores();
ProfesorManager manager = ProfesorManager.getInstance();
manager.getProfesores();

Con esto ya nos aseguramos de que en todas las clases de Vista hagamos referencia a las mismas instancias únicas de las clases Manager.

Sabiendo ya todo esto, veamos como quedan las clases Vista de nuestro Gestor:

UniApp

package view;

import java.util.Scanner;

public class UniApp {
	
	public AlumnoView alumnoView = new AlumnoView();
	public ProfesorView profesorView = new ProfesorView();
	public AsignaturaView asignaturaView = new AsignaturaView();
	
	public void lanzarAplicacion() {
		boolean close = false;
		Scanner scanner = new Scanner(System.in);
		try {
			while (!close) {
				mostrarMenuPrincipal();		
				int choice = Integer.valueOf(scanner.nextLine());			
				switch  (choice) {
				case 1:
					alumnoView.gestionAlumnos(scanner);
					break;
				case 2:
					profesorView.gestionProfesores(scanner);
					break;
				case 3:
					asignaturaView.gestionAsignaturas(scanner);
					break;
				case 0:
					close = true;
					break;
				}
			}
		} catch (Exception e) {
			System.out.println(e.getMessage());
		} finally {
			scanner.close();
		}
	}

	private void mostrarMenuPrincipal() {
		System.out.println("|================================|");
		System.out.println("|       MENU DE SELECCION        |");
		System.out.println("|================================|");
		System.out.println("| Opciones:                      |");
		System.out.println("|     1. Gestión de Alumnos      |");
		System.out.println("|     2. Gestión de Profesores   |");
		System.out.println("|     3. Gestión de Asignaturas  |");
		System.out.println("|     0. Salir                   |");
		System.out.println("|================================|");
	}

}

CommonView

package view;

import java.util.Scanner;

import exception.UniversidadException;
import manager.asignatura.AsignaturaManager;
import manager.persona.ProfesorManager;
import model.asignatura.Asignatura;
import model.persona.Profesor;

public abstract class CommonView {

	public Profesor getProfesor(Scanner scanner) throws UniversidadException {
		Profesor profesor = null;
		System.out.println("Introduzca el DNI del profesor: ");
		try {
			String dni = scanner.nextLine();
			profesor = ProfesorManager.getInstance().getProfesor(dni);
		} catch (IndexOutOfBoundsException e) {
			throw new UniversidadException("Profesor no encontrado");
		}
		return profesor;	
	}
	
	public Asignatura getAsignatura(Scanner scanner) throws UniversidadException {
		Asignatura asignatura = null;
		System.out.println("Introduzca el nombre de la asignatura");
		String nombre = scanner.nextLine();
		asignatura = AsignaturaManager.getInstance().getAsignatura(nombre);
		if (asignatura == null)
			throw new UniversidadException("Asignatura no encontrada");
		return asignatura;
	}
}

PersonaView

package view;

import java.util.Scanner;

import exception.UniversidadException;
import model.persona.Persona;

public abstract class PersonaView extends CommonView {

	protected void editPersona(Scanner scanner, Persona persona) {
		System.out.println("¿Quiere cambiar el nombre? (S/N)");
		String opcion = scanner.nextLine();
		if ("S".equals(opcion)) {
			System.out.println("Introduzca el nombre");
			persona.setNombre(scanner.nextLine());
		}
		System.out.println("¿Quiere cambiar el apellido? (S/N)");
		opcion = scanner.nextLine();
		if ("S".equals(opcion)) {
			System.out.println("Introduzca el apellido");
			persona.setApellidos(scanner.nextLine());
		}
		System.out.println("¿Quiere cambiar el DNI? (S/N)");
		opcion = scanner.nextLine();
		if ("S".equals(opcion)) {
			System.out.println("Introduzca el DNI");
			persona.setDni(scanner.nextLine());
		}
		System.out.println("¿Quiere cambiar la edad? (S/N)");
		opcion = scanner.nextLine();
		if ("S".equals(opcion)) {
			System.out.println("Introduzca la edad");
			persona.setEdad(Integer.valueOf(scanner.nextLine()));
		}
		System.out.println("Quiere cambiar el sexo? (S/N)");
		opcion = scanner.nextLine();
		if ("S".equals(opcion)) {
			System.out.println("Introduzca el sexo");
			persona.setSexo(scanner.nextLine());
		}
	}
	
	protected Persona getPersonaData(Scanner scanner) throws UniversidadException {
		System.out.println("Introduzca el nombre: ");
		String nombre = scanner.nextLine();
		System.out.println("Introduzca los apellidos: ");
		String apellidos = scanner.nextLine();
		System.out.println("Introduzca el DNI: ");
		String dni = scanner.nextLine();
		System.out.println("Introduzca el sexo: ");
		String sexo = scanner.nextLine();	
		System.out.println("Introduzca la edad: ");
		int edad = -1;
		try {
			edad = Integer.valueOf(scanner.nextLine());	
			if (edad < 0) {
				throw new UniversidadException("La edad tiene que ser positiva");
			}
		} catch (NumberFormatException e) {
			throw new UniversidadException("Tiene que introducir caracteres numéricos en la edad");
		}
		
		return new Persona(nombre, apellidos, dni, edad, sexo);
	}
}

ProfesorView

package view;

import java.util.List;
import java.util.Scanner;

import exception.UniversidadException;
import manager.persona.ProfesorManager;
import model.persona.Persona;
import model.persona.Profesor;

public class ProfesorView extends PersonaView {

	public void gestionProfesores(Scanner scanner) throws UniversidadException {		
		boolean close = false;
		while (!close) {
			mostrarMenuProfesores();
			int choice = Integer.valueOf(scanner.nextLine());		
			switch  (choice) {
			case 1: 
				listarProfesores();
				break;
			case 2:
				addProfesor(scanner);
				break;
			case 3:
				buscarProfesor(scanner);
				break;
			case 4:
				editarProfesor(scanner);
				break;
			case 5:
				borrarProfesor(scanner);
				break;
			case 0:
				close = true;
				break;
			}
		}
	}
	
	private void mostrarMenuProfesores() {
		System.out.println("|================================|");
		System.out.println("|            PROFESORES          |");
		System.out.println("|================================|");
		System.out.println("| Opciones:                      |");
		System.out.println("|     1. Listar Profesores       |");
		System.out.println("|     2. Añadir Profesor         |");
		System.out.println("|     3. Buscar Profesor         |");
		System.out.println("|     4. Editar Profesor         |");
		System.out.println("|     5. Borrar Profesor         |");
		System.out.println("|     0. Salir                   |");
		System.out.println("|================================|");
	}
	
	private void listarProfesores() {
		System.out.println("|================================|");
		System.out.println("|       LISTADO PROFESORES       |");
		System.out.println("|================================|");
		List<Profesor> lsProfesores = ProfesorManager.getInstance().getProfesores();
		if (lsProfesores.size() > 0) {
			for (Profesor profesor : lsProfesores)
				System.out.println(profesor.toString());
		} else 
			System.out.println("No hay profesores almacenados");
	}
	
	private void addProfesor(Scanner scanner) throws UniversidadException {
		System.out.println("|================================|");
		System.out.println("|    INSERCCION DE PROFESORES    |");
		System.out.println("|================================|");		
		Persona p = getPersonaData(scanner);
		Profesor profesor = new Profesor(p);
		ProfesorManager.getInstance().insertar(profesor);
	}
	
	private void editarProfesor(Scanner scanner) throws UniversidadException {
		System.out.println("|================================|");
		System.out.println("|      EDICIÓN DE PROFESORES     |");
		System.out.println("|================================|");	
		Profesor profesor = getProfesor(scanner);
		editPersona(scanner, profesor);
	}
	
	private void borrarProfesor(Scanner scanner) throws UniversidadException {
		Profesor profesor = getProfesor(scanner);
		System.out.println("¿De verdad quiere borrar a este profesor? (S/N)");
		String opcion = scanner.nextLine();
		if ("S".equals(opcion))
			ProfesorManager.getInstance().borrar(profesor);
	}
	
	private void buscarProfesor(Scanner scanner) throws UniversidadException {
		System.out.println("|================================|");
		System.out.println("|     BUSQUEDA DE PROFESORES     |");
		System.out.println("|================================|");
		Profesor profesor = getProfesor(scanner);
		System.out.println(profesor.toString());
	}
	
	
}

AlumnoView

package view;

import java.util.List;
import java.util.Scanner;

import exception.UniversidadException;
import manager.persona.AlumnoManager;
import model.asignatura.Asignatura;
import model.persona.Alumno;
import model.persona.Persona;

public class AlumnoView extends PersonaView {

	public void gestionAlumnos(Scanner scanner) throws UniversidadException {
		boolean close = false;
		while (!close) {
			mostrarMenuAlumnos();
			int choice = Integer.valueOf(scanner.nextLine());			
			switch  (choice) {
			case 1:
				listarAlumnos();
				break;
			case 2:
				addAlumno(scanner);
				break;
			case 3:
				buscarAlumno(scanner);
				break;
			case 4:
				asignarAsignatura(scanner);
				break;
			case 5:
				editarAlumno(scanner);
				break;
			case 6:
				borrarAlumno(scanner);
				break;
			case 0:
				close = true;
				break;
			}
		}	
	}
	
	private void mostrarMenuAlumnos() {
		System.out.println("|================================|");
		System.out.println("|             ALUMNOS            |");
		System.out.println("|================================|");
		System.out.println("| Opciones:                      |");
		System.out.println("|     1. Listar Alumnos          |");
		System.out.println("|     2. Añadir Alumno           |");
		System.out.println("|     3. Buscar Alumno           |");
		System.out.println("|     4. Asociar Asignaturas     |");
		System.out.println("|     5. Editar Alumno           |");
		System.out.println("|     6. Borrar Alumno           |");
		System.out.println("|     0. Salir                   |");
		System.out.println("|================================|");
	}
	
	private void listarAlumnos() {
		System.out.println("|================================|");
		System.out.println("|        LISTADO ALUMNOS         |");
		System.out.println("|================================|");
		List<Alumno> lsAlumnos = AlumnoManager.getInstance().getAlumnos();
		if (lsAlumnos.size() > 0) {
			for (Alumno alumno : lsAlumnos) 
				System.out.println(alumno.toString());	
		} else {
			System.out.println("No hay alumnos almacenados");
		}
	}
	
	private void addAlumno(Scanner scanner) throws UniversidadException {
		System.out.println("|================================|");
		System.out.println("|     INSERCCION DE ALUMNOS      |");
		System.out.println("|================================|");		
		Persona p = getPersonaData(scanner);
		Alumno alumno = new Alumno(p);
		AlumnoManager.getInstance().insertar(alumno);
	}
	
	private void buscarAlumno(Scanner scanner) throws UniversidadException {
		System.out.println("|================================|");
		System.out.println("|       BUSQUEDA DE ALUMNOS      |");
		System.out.println("|================================|");
		Alumno alumno = getAlumno(scanner);
		System.out.println(alumno.toString());
	}
	
	private void editarAlumno(Scanner scanner) throws UniversidadException {
		System.out.println("|================================|");
		System.out.println("|        EDICIÓN DE ALUMNOS      |");
		System.out.println("|================================|");	
		Alumno alumno = getAlumno(scanner);
		editPersona(scanner, alumno);
	}
	
	private Alumno getAlumno(Scanner scanner) throws UniversidadException {
		Alumno alumno = null; 
		System.out.println("Introduzca el DNI del alumno: ");
		try {
			String dni = scanner.nextLine();
			alumno = AlumnoManager.getInstance().getAlumno(dni);
		} catch (IndexOutOfBoundsException e) {
			throw new UniversidadException("Alumno no encontrado");
		}
		return alumno;
	}
	
	private void borrarAlumno(Scanner scanner) throws UniversidadException {
		Alumno alumno = getAlumno(scanner);
		System.out.println("¿De verdad quiere borrar a este alumno? (S/N)");
		String opcion = scanner.nextLine();
		if ("S".equals(opcion))
			AlumnoManager.getInstance().borrar(alumno);

	}
	
	private void asignarAsignatura(Scanner scanner) throws UniversidadException {
		System.out.println("|================================|");
		System.out.println("|       ASIGNAR ASIGNATURAS      |");
		System.out.println("|================================|");
		Alumno alumno = getAlumno(scanner);
		Asignatura asignatura = getAsignatura(scanner);
		System.out.println("¿Está seguro? Y/N");
		String choice = scanner.nextLine();
		if (choice.equals("Y")) {
			alumno.asignarAsignatura(asignatura);
		}
	}
}

AsignaturaView

package view;

import java.util.List;
import java.util.Scanner;

import exception.UniversidadException;
import manager.asignatura.AsignaturaManager;
import model.asignatura.Asignatura;
import model.persona.Profesor;

public class AsignaturaView extends CommonView {
	
	public void gestionAsignaturas(Scanner scanner) throws UniversidadException {
		boolean close = false;
		while (!close) {
			mostrarMenuAsignaturas();
			int choice = Integer.valueOf(scanner.nextLine());		
			switch (choice) {
			case 1:
				listarAsignaturas();
				break;
			case 2:
				addAsignatura(scanner);
				break;
			case 3:
				buscarAsignatura(scanner);
				break;
			case 4:
				editarAsignatura(scanner);
				break;
			case 5:
				borrarAsignatura(scanner);
				break;
			case 0:
				close = true;
				break;
			}		
		}
	}
	
	private void mostrarMenuAsignaturas() {
		System.out.println("|================================|");
		System.out.println("|          ASIGNATURAS           |");
		System.out.println("|================================|");
		System.out.println("| Opciones:                      |");
		System.out.println("|     1. Listar Asignaturas      |");
		System.out.println("|     2. Añadir Asignatura       |");
		System.out.println("|     3. Buscar Asignatura       |");
		System.out.println("|     4. Editar Asignatura       |");
		System.out.println("|     5. Borrar Asignatura       |");
		System.out.println("|     0. Salir                   |");
		System.out.println("|================================|");
	}

	private void listarAsignaturas() {
		System.out.println("|================================|");
		System.out.println("|       LISTADO ASIGNATURAS      |");
		System.out.println("|================================|");	
		List<Asignatura> lsAsignaturas = AsignaturaManager.getInstance().getAsignaturas();
		if (lsAsignaturas.size() > 0) {
			for (int i = 0; i < lsAsignaturas.size(); i++) {
				Asignatura asig = lsAsignaturas.get(i);
				System.out.println(i + ". " + asig.getNombre());
			}
		} else {
			System.out.println("No hay asignaturas almacenadas");
		}
	}
	
	private void addAsignatura(Scanner scanner) throws UniversidadException {
		System.out.println("|================================|");
		System.out.println("|    INSERCCION DE ASIGNATURAS   |");
		System.out.println("|================================|");			
		System.out.println("Introduzca el nombre: ");
		String nombre = scanner.nextLine();
		System.out.println("Introduzca el curso: ");
		int curso = Integer.valueOf(scanner.nextLine());
		Profesor profesor = getProfesor(scanner);
		Asignatura asignatura = new Asignatura(nombre, curso, profesor);
		AsignaturaManager.getInstance().insertar(asignatura);
	}
	
	private void buscarAsignatura(Scanner scanner) throws UniversidadException {
		System.out.println("|================================|");
		System.out.println("|     BUSQUEDA DE ASIGNATURAS    |");
		System.out.println("|================================|");
		Asignatura asignatura = getAsignatura(scanner);
		System.out.println(asignatura.toString());
	}

	private void editarAsignatura(Scanner scanner) throws UniversidadException {
		System.out.println("|================================|");
		System.out.println("|     EDICIÓN DE ASIGNATURAS     |");
		System.out.println("|================================|");	
		Asignatura asignatura = getAsignatura(scanner);
		System.out.println("¿Quiere cambiar el nombre? (S/N)");
		String opcion = scanner.nextLine();
		if ("S".equals(opcion)) {
			System.out.println("Introduzca el nombre");
			asignatura.setNombre(scanner.nextLine());
		}
		System.out.println("¿Quiere cambiar el curso? (S/N)");
		opcion = scanner.nextLine();
		if ("S".equals(opcion)) {
			System.out.println("Introduzca el curso");
			asignatura.setCurso(Integer.valueOf(scanner.nextLine()));
		}
		System.out.println("¿Quiere cambiar al profesor? (S/N)");
		opcion = scanner.nextLine();
		if ("S".equals(opcion)) {
			Profesor profesor = getProfesor(scanner);
			asignatura.setProfesor(profesor);
		}
	}
		
	private void borrarAsignatura(Scanner scanner) throws UniversidadException {
		Asignatura asignatura = getAsignatura(scanner);
		System.out.println("¿De verdad quiere borrar esta asignatura? (S/N)");
		String opcion = scanner.nextLine();
		if ("S".equals(opcion))
			AsignaturaManager.getInstance().borrar(asignatura);
	}
	
}

Clase Launcher

package main;

import view.UniApp;

public final class Launcher {
	public static void main (String[] args) {
		UniApp app = new UniApp();
		app.lanzarAplicacion();
	}
}

A parte de lo que ya se ha expuesto, hay que destacar la creación de dos clases abstractas, CommonView y PersonaView, que aglutinan métodos comunes utilizados en las clases Vista que heredan de ellas. El objetivo de esta decisión es principalmente el de no duplicar código.

En cuanto al tratamiento de excepciones, se siguen propagando las excepciones en las clases de vista por entidad igual que se ha hecho en los modelos y los manager. Es en la clase principal vista, UniApp, donde se tiene un try-catch que hace el tratamiento idóneo para cualquier excepción que tenga lugar en la aplicación. De esta forma centralizamos el tratamiento de errores en la última capa, no dejándonos ninguna excepción no controlada en el resto de capas. Ese bloque try-catch, además, contiene un bloque finally donde cerramos el objeto Scanner, encargado en recoger los eventos de teclado.

Hay que hacer una mención especial a esta clase Scanner: Es creada y cerrada en la vista principal, y se pasa al resto de subvistas y métodos asociados. Se hace esto ya que sólo debe de existir una instancia de Scanner por aplicación. Tener más de una puede ocasionar errores en sus cierres y usos posteriores.

Y ya está, ya tenemos una aplicación completa en Java que interacciona con el usuario a través de la consola. Tenéis todo el código en mi repositorio de Github, por si queréis bajároslo y probarlo vosotros mismos. Si aún no sabéis como hacerlo no os preocupéis. Dedicaré la próxima pildorita para explicar un poco como utilizar Eclipse, el entorno de desarrollo por excelencia de Java.

Índice de Pildoritas

Share