Composition, agrégation et association en Java

1. Introduction

Les objets ont des relations entre eux, à la fois dans la vie réelle et dans la programmation. Parfois, il est difficile de comprendre ou de mettre en œuvre ces relations.

Dans ce didacticiel, nous allons nous concentrer sur la vision de Java sur trois types de relations parfois facilement mélangés: composition, agrégation et association.

2. Composition

La composition est un type de relation «appartenant à». Cela signifie que l'un des objets est une structure logiquement plus grande, qui contient l'autre objet. En d'autres termes, il fait partie ou fait partie de l'autre objet.

Alternativement, nous l'appelons souvent une relation «a un» (par opposition à une relation «est-un», qui est l'héritage).

Par exemple, une pièce appartient à un bâtiment, ou en d'autres termes un bâtiment possède une pièce. Donc, fondamentalement, que nous l'appelions «appartient à» ou «a un» n'est qu'une question de point de vue.

La composition est une sorte de relation «has-a» forte parce que l'objet contenant la possède. Par conséquent, les cycles de vie des objets sont liés. Cela signifie que si nous détruisons l'objet propriétaire, ses membres seront également détruits avec lui. Par exemple, la pièce est détruite avec le bâtiment dans notre exemple précédent.

Notez que cela ne signifie pas que l'objet conteneur ne peut exister sans aucune de ses parties. Par exemple, nous pouvons abattre tous les murs à l'intérieur d'un bâtiment, donc détruire les pièces. Mais le bâtiment existera toujours.

En termes de cardinalité, un objet contenant peut avoir autant de parties que nous le souhaitons. Cependant, toutes les pièces doivent avoir exactement un conteneur .

2.1. UML

En UML, nous indiquons la composition avec le symbole suivant:

Notez que le losange est au niveau de l'objet contenant et qu'il est la base de la ligne, pas une pointe de flèche. Par souci de clarté, nous dessinons souvent aussi la pointe de flèche:

Ainsi, nous pouvons utiliser cette construction UML pour notre exemple Building-Room:

2.2. Code source

En Java, nous pouvons modéliser cela avec une classe interne non statique:

class Building { class Room {} }

Alternativement, nous pouvons également déclarer cette classe dans un corps de méthode. Peu importe qu'il s'agisse d'une classe nommée, d'une classe anonyme ou d'un 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(); } }

Notez qu'il est essentiel que notre classe interne ne soit pas statique car elle lie toutes ses instances à la classe conteneur.

En général, l'objet conteneur souhaite accéder à ses membres. Par conséquent, nous devons stocker leurs références:

class Building { List rooms; class Room {} }

Notez que tous les objets de classe interne stockent une référence implicite à leur objet conteneur. Par conséquent, nous n'avons pas besoin de le stocker manuellement pour y accéder:

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

3. Agrégation

L'agrégation est également une relation «has-a». Ce qui le distingue de la composition, c'est qu'il n'implique pas de posséder. En conséquence, les cycles de vie des objets ne sont pas liés: chacun d'eux peut exister indépendamment les uns des autres.

Par exemple, une voiture et ses roues. Nous pouvons décoller les roues, et elles existeront toujours. Nous pouvons monter d'autres roues (préexistantes) ou les installer sur une autre voiture et tout fonctionnera très bien.

Bien sûr, une voiture sans roues ou une roue détachée ne sera pas aussi utile qu'une voiture avec ses roues. Mais c'est pourquoi cette relation existait en premier lieu: assembler les pièces en une construction plus grande, capable de plus de choses que de ses parties .

Étant donné que l'agrégation n'implique pas la possession, un membre n'a pas besoin d'être lié à un seul conteneur . Par exemple, un triangle est composé de segments. Mais les triangles peuvent partager des segments comme leurs côtés.

3.1. UML

L'agrégation est très similaire à la composition. La seule différence logique est que l'agrégation est une relation plus faible.

Par conséquent, les représentations UML sont également très similaires. La seule différence est que le diamant est vide:

Pour les voitures et les roues, alors, nous ferions:

3.2. Code source

En Java, nous pouvons modéliser l'agrégation avec une simple référence ancienne:

class Wheel {} class Car { List wheels; }

The member can be any type of class, except a non-static inner class.

In the code snippet above both classes have their separate source file. However, we can also use a static inner class:

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

Note that Java will create an implicit reference only in non-static inner classes. Because of that, we have to maintain the relationship manually where we need it:

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

4. Association

Association is the weakest relationship between the three. It isn't a “has-a” relationship, none of the objects are parts or members of another.

Association only means that the objects “know” each other. For example, a mother and her child.

4.1. UML

In UML, we can mark an association with an arrow:

If the association is bidirectional, we can use two arrows, an arrow with an arrowhead on both ends, or a line without any arrowheads:

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 relation entre professeurs est association car cela n'a aucun sens de dire qu'un professeur fait partie d'un autre.

En conséquence, nous pouvons modéliser cet exemple avec le diagramme UML suivant:

Et le code Java ressemble à ceci:

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

Notez que si nous nous appuyons sur les termes «a», «appartient à», «membre de», «partie de» , etc., nous pouvons plus facilement identifier les relations entre nos objets.

7. Conclusion

Dans cet article, nous avons vu les propriétés et la représentation de la composition, de l'agrégation et de l'association. Nous avons également vu comment modéliser ces relations en UML et Java.

Comme d'habitude, les exemples sont disponibles à l'adresse over sur GitHub.