Tabla de contenidos
Los encargados de la piscina de la UC3M le han pedido que programe una aplicación Windows para gestionar a los usuarios de la piscina.
La aplicación tendrá una sencilla base de datos con todos los usuarios de la piscina y será capaz de mostrar una lista completa del contenido de la base de datos: todos los usuarios con todos sus detalles conocidos.
Existen 5 tipos de usuarios de la piscina:
Outsider: Externos, son personas que no tienen relación directa con la universidad, por ejemplo, vecinos de Leganés. De ellos solo se conoce su DNI.
Staff: Personal de administración y servicios, son trabajadores de la universidad que no imparten docencia. Además de su DNI, se conoce su sueldo.
Professor: Profesores, también son trabajadores de la universidad, pero se encargan de la docencia. Además de su DNI, se conoce el departamento al que pertenecen y su sueldo.
Student: Alumnos, además de su DNI, se conoce su NIA.
Intern: Becarios, son alumnos que han sido contratados temporalmente por la universidad. Además de sus datos como alumno, se conoce también su sueldo.
¿Cúal de las siguientes figuras le parece más apropiada para describir la jerarquía de clases de la aplicación? Valore cada una de ellas según distintos criterios que se le ocurran y discútalos con un compañero. Dedíquele unos 15 minutos.
No hay respuesta perfecta para este apartado, pero si una discusión interesante.
La A es un diseño sin jerarquías, básico pero poco versátil. La
redundancia de código es elevada, por ejemplo, en el manejo de
los atributos de clase dni
and salary
,
presentes en casi todas las clases.
Además, las clases no representan con fidelidad algunos detalles
del enunciado, como que los miembros de la clase
Intern
(becarios) son también Student
(estudiantes).
La B si utiliza jerarquías, pero muy sencillas y tiene algunos problemas:
La acepción americana de "Staff" no admite personal
docente, sin embargo en el diagrama los profesores son
una subclase de Staff
. El enunciado explica
claramente que los Professor
no deberían ser
Staff
.
Hay demasiada redundancia con el DNI y los salarios, ya que aparecen en casi todas las clases.
En la C se ha sustituido a Outsider
por
Person
y se ha hecho a esta nueva clase la clase
básica de la que derivan todas las demás. Esto permite eliminar
la redundancia del DNI, a costa de ignorar ligeramente la
semántica del enunciado. Se ha de suponer que las instancias de
Person
serán "Externos", a falta de más datos. Esto
parece razonable dada la utilidad de la aplicación, pero podría
dar problemas en el futuro si se quiere ampliar o modificar la
aplicación.
Seguimos teniendo el problema de que los profesores son
Staff
y la redundancia del salario.
La D parece la más apropiada, no hay redundancia con el DNI y
los profesores ya no son Staff
. Sigue siendo una
opción incómoda por la redundancia del sueldo, que aparece en
muchas clases.
La E resuelve también el problema del sueldo, mediante la clase
Worker
. Desgraciadamente, en Java no existe la
herencia múltiple, por lo que este diagrama no es viable. En
proximas clases de la asignatura aprenderás a implementar
soluciones como las de esta figura, utilizando lo que se conoce
como Interfaces. Sin embargo, con lo que se
ha visto en clase por el momento, esta solución no es viable.
En resumen: nos quedamos con D, a pesar de su redundancia con el salario, ya que de momento, no sabemos hacerlo mejor.
Implemente la aplicación utilizando el modelo de la figura D.
Programe todas las clases. Además de la declaración de sus
atributos y de un constructor, cada clase debe proporcionar un
método String toString()
que genere una
representación textual de la información conocida del usuario
según el formato que se muestra más abajo.
En una clase de prueba aparte, que contenga únicamente un método
main
, vaya creando distintos objetos de usuarios
(los listados más abajo) para probar la aplicación. Incluya
estos objetos en una variable que sea capaz de almacenar varios
objetos de la clase Person
(cualquier contenedor
Java servirá, por ejemplo un objeto de la clase ArrayList<Person>
...)
Luego recorra todos los elementos del contenedor y escriba los
detalles de cada usuario en la salida estándar.
El comportamiento esperado de su aplicación es el siguiente (el orden en que imprima los usuarios es irrelevante):
C:\Users\Alberto>java DataBase DNI: 01100000-A DNI: 00220000-B DNI: 00030000-C, salary: 2000 DNI: 04040000-D, salary: 1500 DNI: 50500000-E, salary: 1000, department: mathematics DNI: 66600000-F, salary: 2000, department: telematics DNI: 77000000-G, NIA: 777777 DNI: 88080000-H, NIA: 888888 DNI: 90990000-I, NIA: 999999, salary: 400 DNI: 10100000-J, NIA: 101010, salary: 800
#### Person.java ##################### public class Person { private String dni; public Person(String dni) { this.dni = dni; } public String toString() { return "DNI: " + dni; } } #### Staff.java ###################### public class Staff extends Person { private int salary; public Staff(String dni, int salary) { super(dni); this.salary = salary; } public String toString() { return super.toString() + ", salary: " + salary; } } #### Professor.java #################### public class Professor extends Staff { private String department; public Professor(String dni, int salary, String department) { super(dni, salary); this.department = department; } public String toString() { return super.toString() + ", department: " + department; } } #### Student.java #################### public class Student extends Person { private String nia; public Student(String dni, String nia) { super(dni); this.nia = nia; } public String toString() { return super.toString() + ", NIA: " + nia; } } #### Intern.java ##################### public class Intern extends Student { private int salary; public Intern(String dni, String nia, int salary) { super(dni, nia); this.salary = salary; } public String toString() { return super.toString() + ", salary: " + salary; } } #### DataBase.java ################### import java.util.ArrayList; public class DataBase { public static void main(String args[]) { // All users are stored in the same collection, any Java // collection will do for this simple exercise ArrayList<Person> users = new ArrayList<Person>(); // Fill the database with all users' data { Person p = new Person("01100000-A"); users.add(p); p = new Person("00220000-B"); users.add(p); Staff s = new Staff("00030000-C", 2000); users.add(s); s = new Staff("04040000-D", 1500); users.add(s); Professor f = new Professor("50500000-E", 1000, "mathematics"); users.add(f); f = new Professor("66600000-F", 2000, "telematics"); users.add(f); Student st = new Student("77000000-G", "777777"); users.add(st); st = new Student("88080000-H", "888888"); users.add(st); Intern in = new Intern("90990000-I", "999999", 400); users.add(in); in = new Intern("10100000-J", "101010", 800); users.add(in); } // Print all users for (int i=0; i<users.size(); i++) { Person p = users.get(i); System.out.println(p); } } }
Discuta con su compañero cómo es posible que a pesar de
utlizar una referencia a Person
a la hora de
imprimir cada usuario, se esté ejecutando el método
toString()
de su clase original correspondiente
(Staff
, Professor
,
Student
...)
Discuta con su compañero por qué es necesario declarar el
método String toString()
como público en
Person
y sus subclases, en lugar de usar otros
modificadores de acceso que podrían ser más adecuados para
proteger el acceso a este método (como
package-private
, por ejemplo).
En Java, la resolución de métodos sobreescritos se realiza mediante invocación virtual de métodos, lo que quiere decir que las invocaciones de métodos se resuelven mediante la tabla de métodos de la clase original del objeto, independientemente del tipo de la referencia que estemos usando para manejarlo.
El método String toString()
que se quiere
sobreescribir, está definido en la clase Object
como público. En Java, los modificadores de acceso forman
parte de la signatura de los métodos. Esto quiere decir que
no nos queda más remedio que mantener el modificador de
acceso original al sobreescribir un método.
A falta de unas pocas horas para la fecha de entrega de su aplicación, la UC3M le pide 2 modificaciones extra:
Añada un nuevo tipo de usuario Tenured (Profesor funcionario), que sea como un profesor, pero con un sueldo fijo de 2500 Euros. Esto quiere decir que el constructor de esta clase no debe recibir el sueldo como argumento. Añada estos dos ejemplos de profesores funcionarios a la base de datos:
DNI: 11110000-K, salary: 2500, department: geography DNI: 12120000-L, salary: 2500, department: mathematics
Cuando se lanze la aplicación con el argumento de línea de comandos
"-s
" (del inglés short) en
vez de listarse todos los detalles de los usuarios, se debe
imprimir un listado resumido, únicamente con los datos
disponibles desde la clase básica Person
.
El comportamiento esperado por la nueva versión de la aplicación es el siguiente:
; java DataBase DNI: 01100000-A DNI: 00220000-B DNI: 00030000-C, salary: 2000 DNI: 04040000-D, salary: 1500 DNI: 50500000-E, salary: 1000, department: mathematics DNI: 66600000-F, salary: 2000, department: telematics DNI: 77000000-G, NIA: 777777 DNI: 88080000-H, NIA: 888888 DNI: 90990000-I, NIA: 999999, salary: 400 DNI: 10100000-J, NIA: 101010, salary: 800 DNI: 11110000-K, salary: 2500, department: geography DNI: 12120000-L, salary: 2500, department: mathematics ; java DataBase -s DNI: 01100000-A DNI: 00220000-B DNI: 00030000-C DNI: 04040000-D DNI: 50500000-E DNI: 66600000-F DNI: 77000000-G DNI: 88080000-H DNI: 90990000-I DNI: 10100000-J DNI: 11110000-K DNI: 12120000-L
Estime cuánto tiempo necesitará para implementar cada uno de estos cambios por separado, en horas o minutos.
Implemente todos estos cambios en una nueva versión de la aplicación y compare el tiempo empleado con su estimación inicial.
Añadir Tenured
es sencillo: se hereda de
Professor
, se define una constante para su sueldo y
se utiliza para llamar al constructor de su superclase. En total
unos 5 o 10 minutos. Atención: no es necesario sobreescribir el
método String toString()
en esta nueva clase.
Detectar el argumento "-s
" es también
sencillo. Basta con añadir la lógica necesaria para procesar los
argumentos de entrada del comando, detectar el uso de
-s
e incluir algunos mensajes de error para
argumentos no válidos.
La manera más rápida de implementar el listado resumido que se
debe activar con el argumento -s
es añadir un nuevo
método a Person
que imprima un listado resumido. El
resto de las subclases heredarán este método y toda su
funcionalidad sin necesidad de modificarlas. En total, entre 10
y 15 minutos.
#### Tenured.java ##################### public class Tenured extends Professor { private static final int TENURED_SALARY = 2500; public Tenured(String dni, String department) { super(dni, TENURED_SALARY, department); } } #### Person.java ##################### public class Person { private String dni; public Person(String dni) { this.dni = dni; } public String toString() { return "DNI: " + dni; } public String toStringShort() { // return toString(); -- WRONG // return this.toString(); -- WRONG // // Sadly, none of these will work, as virtual method invocation // for subclasses will use their own subclass.toString() method, // hence code redundancy is unavoilable here return "DNI: " + dni; } } #### DataBase.java ################### import java.util.ArrayList; public class DataBase { public static void main(String args[]) { // By default, listings are in long format boolean shortListingDesired = false; // Parse command args if (args.length > 1) { System.err.println("Invalid number of arguments"); return; } else if (args.length == 1) { if (args[0].equals("-s")) { shortListingDesired = true; } else { System.err.println("Invalid argument: " + args[0]); return; } } // All users are stored in the same collection, any Java // collection will do for this simple exercise ArrayList<Person> users = new ArrayList<Person>(); // Fill the database with all users' data { Person p = new Person("01100000-A"); users.add(p); p = new Person("00220000-B"); users.add(p); Staff s = new Staff("00030000-C", 2000); users.add(s); s = new Staff("04040000-D", 1500); users.add(s); Professor f = new Professor("50500000-E", 1000, "mathematics"); users.add(f); f = new Professor("66600000-F", 2000, "telematics"); users.add(f); Student st = new Student("77000000-G", "777777"); users.add(st); st = new Student("88080000-H", "888888"); users.add(st); Intern in = new Intern("90990000-I", "999999", 400); users.add(in); in = new Intern("10100000-J", "101010", 800); users.add(in); Tenured t = new Tenured("11110000-K", "geography"); users.add(t); t = new Tenured("12120000-L", "mathematics"); users.add(t); } // Print all users for (int i=0; i<users.size(); i++) { Person p = users.get(i); if (shortListingDesired) System.out.println(p.toStringShort()); else System.out.println(p); } } }