Pildoritas de POO y Java: Relaciones entre Clases

En la anterior pildorita os dije que hablaríamos de la Herencia y el Polimorfismo, y aunque esa pildorita está prácticamente terminada en borradores, dándole vueltas a las siguientes pildoritas, me daba cuenta que la que tenía planteada como continuación debía de ir antes, es decir, ésta, sobre relaciones entre clases. Así que nada, esto significa más espera entre la anterior pildorita y ésta, pero el lado bueno es que la siguiente la sacaré en breves.

Llevo unos 7 años trabajando en el desarrollo software y he pasado por multitud de proyectos de gran envergadura. Si algo tengo claro en todos estos años, es que se hace muy mal código – por diversos motivos que no vienen al caso – y que dónde más se falla es, precisamente, en las relaciones de clases: códigos espagueti, pésima modularización y encapsulación… Todos esos problemas vienen, en la mayoría de casos, porque no se tienen del todo claro las relaciones entre clases en la parte más importante del desarrollo, el diseño. Es por eso que antes de entrar en temas de Herencia (algo que muchos desarrollores sí que comprenden bien), creo que es mejor para el aprendizaje de los anteriores conceptos que veamos este tema, que permite asentarlos. Ya habrá tiempo de entrar en la siguiente relación, la generalización, ya que la Herencia no deja de ser también una relación de dos clases.

En la anterior pildorita vimos lo que era un mensaje, y que cuando un objeto lanza un mensaje a otro, estos se comunican y colaboran entre sí. Se dice que existe una Relación entre dos clases si dos objetos de las respectivas clases colaboran entre sí.

Hay tres tipos de relaciones entre clases que a continuación veremos de forma más detallada: Composición, asociación y uso.

Para mayor comprensión de las mismas, utilizaremos el siguiente ejemplo, donde se pueden ver los tres tipos de relaciones:

public class Alumno {
	private String nombre;
    private String apellidos;
    private String dni;
    private Integer edad;
    private String sexo;
	
	private int codAlumno;
	private Lis<Asignatura> asignaturas;
	
	public Alumno (int codAlumno, String nombre, String apellidos, String dni, Integer edad, String sexo, List<Asignatura> lsAsignaturas) {
		this.nombre = nombre;
    	this.apellidos = apellidos;
    	this.dni = dni;
    	this.edad = edad;
    	this.sexo = sexo;
		this.codAlumno = codAlumno;
		this.asignaturas = lsAsignaturas;
	}

        public void setCodAlumno(int codAlumno) {
		this.codAlumno = codAlumno;
	}
	public int getCodAlumno() {
		return codAlumno;
	}
	public void setAsignaturas(List<Asignatura> asignaturas) {
		this.asignaturas = asignaturas;
	}
	public List<Asignatura> getAsignaturas() {
		return asignaturas;
	}
	
	public void asignarAsignatura(Asignatura asignatura) {
		asignaturas.add(asignatura);
	}

	public double getNotaMedia() {
		double resultado = 0;
		for (int i = 0; i < asignaturas.size(); i++) {
			Asignatura asig = asignaturas.get(i);
			resultado += asig.getNota();
		}
		return Math.round((resultado != 0 ? resultado / asignaturas.size(): 0));
	}
}

 

Composición

Es la relación que se constituye entre el todo y la parte. En otras palabras, existe una relación de composición entre la clase A y la clase B, cuando la clase A “tiene un” objeto de la clase B.

Esta colaboración se traduce en la delegación de la clase todo a las partes de ciertas acciones determinadas para implementar la funcionalidad global.

Con esta colaboración se respetará el principio de encapsulación y modularidad por parte de las clases que componen a la clase A.

A la hora de plasmar en código esta relación con los conceptos que hemos visto en las anteriores pildoritas, esto se traduce en un atributo de la clase B en la clase A. Por tanto, tendrá una visibilidad privada y no es versátil, es decir, no puede ser intercambiable por otros objetos. Así mismo, tendrá una duración en la colaboración no momentánea en el tiempo.

Es el caso del atributo codAlumno de nuestra Clase de ejemplo Alumno. Si hiciéramos una clase Profesor, no tendría sentido introducir un código de alumno, por tanto, no es una entidad versátil. Así mismo, el código de alumno se utilizará en muchas funcionalidades de la clase Alumno, ya que es un identificador único similar al DNI. Así mismo ocurre con el resto de atributos que identifican inequívocamente a un Alumno, como nombre, apellidos, DNI…todos aquellos datos que se inicializan a la hora de construir el objeto y que existirán durante todo el ciclo de vida del objeto Alumno.

 

Uso

Es la relación que se establece de forma momentánea entre un objeto cliente (pide algo) y un objeto servidor (suministra ese algo). Por tanto, el objeto de la clase A enviará un mensaje a un objeto de la clase B en un momento dado sin dependencias futuras. Así mismo, el uso de la clase B no dependerá únicamente de la clase A, sino que podrá ser utilizada por otras muchas clases.

Esto significa que la clase A, la cliente, tendrá como parámetro o valor devuelto de un método un objeto de la clase servidora B si esa colaboración es pública, y como un objeto local dentro de un método si es una colaboración privada. La versatilidad de ser intercambiada la clase B por otra, es, por tanto, elevada.

Por ejemplo, el método getNotaMedia() de la clase Alumno, redondea el resultado final utilizando la clase Math e invocando a su método round. Esta clase no es necesaria obviamente para ese cálculo. Podríamos decidir no redondear, o redondear nosotros creando un método propio que realice esta funcionalidad.
 

Aociación

La asociación es una relación de uso donde la relación entre la clase A y la clase B sí que perdura en el tiempo, es decir, se realiza en diversos momentos del ciclo de vida del objeto A, por lo que se crea una relación de dependencia entre ambos objetos. Al igual que la relación de uso, la responsabilidad de manejar ese objeto B no dependerá únicamente del objeto A, sino que podrá ser utilizado por más objetos de diferentes clases.

La asociación, al ser una relación pública generalmente y de una temporalidad no momentánea, pero teniendo una versatilidad reducida, se traducirá en un atributo en la clase A cliente que apuntará a la clase B servidora.

Un ejemplo de este tipo de relación sería la existente entre el Alumno y las Asignaturas. La clase Asignatura es independiente del Alumno, ya que puede existir sin necesidad de que haya Alumnos matriculados a ella. Así mismo, el Alumno puede estar dado de alta en la universidad sin estar matriculado en ninguna asignatura.
 
Espero que esta pildorita haya sido clara y de verdad afiance conceptos fundamentales ya no en el desarrollo de buenos programas en POO, sino de códigos de calidad bien estructurados y fácilmente mantenibles. Cuando vi este capítulo de la asignatura de Programación II me resultó lioso y no le presté toda la atención que merecía. Me centraba en saber sacar las clases de un problema, definir que atributos tenía, y los métodos y sus funcionalidades. Vaya, estaba anclada en lo que conocía, la imperativa. No fue hasta ya después en Ingeniería del Software que llegué a entender del todo para que servía esto y que, la Programación Orientada a Objetos sin estas relaciones no deja de ser modulada, fallo que luego he visto años más tardo muy extendido en la profesión. Sin entender las relaciones, no se sabe de verdar trabajar con POO y poder de verdad explotar la modularidad y ecapsulación que ofrece.

La siguiente pildorita, sí que sí, será la de Herencia y Polimorfismo, la cual, como comenté antes, está ya acabada, así que en una semana tendréis una nueva dosis javera con la que ya prácticamente habremos acabado esta serie 🙂

Índice de Pildoritas

Share