Universidad Carlos III de Madrid

Grado en Ingeniería Telemática/Grado en Ingeniería de Sistemas de Comunicaciones

Enero-Mayo 2014

Orientación a Objetos y Herencia

Lab Section1. Sesión 4 (laboratorio): Orientación a Objetos y Herencia (II)

Exercise Section1.1. Jerarquías de clases y polimorfismo

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:

  1. 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.

  2. 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.

  3. 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.

  4. Student: Alumnos, además de su DNI, se conoce su NIA.

  5. 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.

Apartado 1. Jerarquía de clases

¿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.

Solución

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:

  1. 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.

  2. 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.

Apartado 2. Polimorfismo

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

Solución

#### 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);
        }
    }
}

Apartado 3.

  1. 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...)

  2. 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).

Solución

  1. 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.

  2. 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.

Apartado 4. Cambios de última hora

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
  1. Estime cuánto tiempo necesitará para implementar cada uno de estos cambios por separado, en horas o minutos.

  2. Implemente todos estos cambios en una nueva versión de la aplicación y compare el tiempo empleado con su estimación inicial.

Solución

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);
        }
    }
}