Composición, agregación y asociación en Java

1. Introducción

Los objetos tienen relaciones entre ellos, tanto en la vida real como en la programación. A veces es difícil comprender o implementar estas relaciones.

En este tutorial, nos centraremos en la versión de Java de tres tipos de relaciones que a veces se mezclan fácilmente: composición, agregación y asociación.

2. Composición

La composición es un tipo de relación de "pertenencia a". Significa que uno de los objetos es una estructura lógicamente más grande, que contiene el otro objeto. En otras palabras, es parte o miembro del otro objeto.

Alternativamente, a menudo lo llamamos una relación "tiene-un" (en oposición a una relación "es-un", que es herencia).

Por ejemplo, una habitación pertenece a un edificio, o en otras palabras, un edificio tiene una habitación. Así que, básicamente, si lo llamamos "pertenece a" o "tiene un" es sólo una cuestión de punto de vista.

La composición es un tipo fuerte de relación "tiene-a" porque el objeto que la contiene es el propietario. Por tanto, los ciclos de vida de los objetos están atados. Significa que si destruimos el objeto propietario, sus miembros también serán destruidos con él. Por ejemplo, la habitación se destruye con el edificio en nuestro ejemplo anterior.

Tenga en cuenta que eso no significa que el objeto contenedor no pueda existir sin ninguna de sus partes. Por ejemplo, podemos derribar todas las paredes dentro de un edificio y, por lo tanto, destruir las habitaciones. Pero el edificio seguirá existiendo.

En términos de cardinalidad, un objeto contenedor puede tener tantas partes como queramos. Sin embargo, todas las piezas deben tener exactamente un contenedor .

2.1. UML

En UML, indicamos composición con el siguiente símbolo:

Tenga en cuenta que el diamante está en el objeto contenedor y es la base de la línea, no una punta de flecha. En aras de la claridad, a menudo también dibujamos la punta de flecha:

Entonces, podemos usar esta construcción UML para nuestro ejemplo Building-Room:

2.2. Código fuente

En Java, podemos modelar esto con una clase interna no estática:

class Building { class Room {} }

Alternativamente, también podemos declarar esa clase en el cuerpo de un método. No importa si se trata de una clase con nombre, una clase anónima o una lambda:

class Building { Room createAnonymousRoom() { return new Room() { @Override void doInRoom() {} }; } Room createInlineRoom() { class InlineRoom implements Room { @Override void doInRoom() {} } return new InlineRoom(); } Room createLambdaRoom() { return () -> {}; } interface Room { void doInRoom(); } }

Tenga en cuenta que es esencial que nuestra clase interna no sea estática, ya que vincula todas sus instancias a la clase contenedora.

Normalmente, el objeto contenedor desea acceder a sus miembros. Por tanto, debemos almacenar sus referencias:

class Building { List rooms; class Room {} }

Tenga en cuenta que todos los objetos de clase interna almacenan una referencia implícita a su objeto contenedor. Como resultado, no necesitamos almacenarlo manualmente para acceder a él:

class Building { String address; class Room { String getBuildingAddress() { return Building.this.address; } } }

3. Agregación

La agregación también es una relación "tiene-a". Lo que lo distingue de la composición es que no implica poseer. Como resultado, los ciclos de vida de los objetos no están atados: cada uno de ellos puede existir independientemente el uno del otro.

Por ejemplo, un automóvil y sus ruedas. Podemos quitar las ruedas y seguirán existiendo. Podemos montar otras ruedas (preexistentes) o instalarlas en otro coche y todo funcionará bien.

Por supuesto, un automóvil sin ruedas o una rueda separada no será tan útil como un automóvil con las ruedas puestas. Pero es por eso que existía esta relación en primer lugar: ensamblar las partes en una construcción más grande, que es capaz de más cosas que sus partes .

Dado que la agregación no implica ser propietario, no es necesario que un miembro esté vinculado a un solo contenedor . Por ejemplo, un triángulo está hecho de segmentos. Pero los triángulos pueden compartir segmentos como lados.

3.1. UML

La agregación es muy similar a la composición. La única diferencia lógica es que la agregación es una relación más débil.

Por lo tanto, las representaciones UML también son muy similares. La única diferencia es que el diamante está vacío:

Para autos y ruedas, entonces haríamos:

3.2. Código fuente

En Java, podemos modelar la agregación con una referencia simple y antigua:

class Wheel {} class Car { List wheels; }

El miembro puede ser cualquier tipo de clase, excepto una clase interna no estática.

En el fragmento de código anterior, ambas clases tienen su archivo fuente independiente. Sin embargo, también podemos usar una clase interna estática:

class Car { List wheels; static class Wheel {} }

Tenga en cuenta que Java creará una referencia implícita solo en clases internas no estáticas. Por eso, tenemos que mantener la relación manualmente donde la necesitemos:

class Wheel { Car car; } class Car { List wheels; }

4. Asociación

La asociación es la relación más débil entre los tres. No es una relación "tiene-a" , ninguno de los objetos es parte o miembro de otro.

Asociación solo significa que los objetos se "conocen" entre sí. Por ejemplo, una madre y su hijo.

4.1. UML

En UML, podemos marcar una asociación con una flecha:

Si la asociación es bidireccional, podemos usar dos flechas, una flecha con punta de flecha en ambos extremos o una línea sin puntas de flecha:

We can represent a mother and her child in UML, then:

4.2. Source Code

In Java, we can model association the same way as aggregation:

class Child {} class Mother { List children; }

But wait, how can we tell if a reference means aggregation or association?

Well, we can't. The difference is only logical: whether one of the objects is part of the other or not.

Also, we have to maintain the references manually on both ends as we did with aggregation:

class Child { Mother mother; } class Mother { List children; }

5. UML Sidenote

For the sake of clarity, sometimes we want to define the cardinality of a relationship on a UML diagram. We can do this by writing it to the ends of the arrow:

Note, that it doesn't make sense to write zero as cardinality, because it means there's no relationship. The only exception is when we want to use a range to indicate an optional relationship:

Also note, that since in composition there's precisely one owner we don't indicate it on the diagrams.

6. A Complex Example

Let's see a (little) more complex example!

We'll model a university, which has its departments. Professors work in each department, who also has friends among each other.

Will the departments exist after we close the university? Of course not, therefore it's a composition.

But the professors will still exist (hopefully). We have to decide which is more logical: if we consider professors as parts of the departments or not. Alternatively: are they members of the departments or not? Yes, they are. Hence it's an aggregation. On top of that, a professor can work in multiple departments.

La relación entre profesores es asociación porque no tiene ningún sentido decir que un profesor es parte de otro.

Como resultado, podemos modelar este ejemplo con el siguiente diagrama UML:

Y el código de Java se ve así:

class University { List department; } class Department { List professors; } class Professor { List department; List friends; }

Tenga en cuenta que si nos basamos en los términos "tiene un", "pertenece a", "miembro de", "parte de" , etc., podemos identificar más fácilmente las relaciones entre nuestros objetos.

7. Conclusión

En este artículo, vimos las propiedades y la representación de la composición, agregación y asociación. También vimos cómo modelar esas relaciones en UML y Java.

Como de costumbre, los ejemplos están disponibles en GitHub.