Les agrégations et les compositions
Dans ce chapitre, nous aborderons les relations de compositions et d’agrégations, qui sont des types particuliers d’associations permettant de représenter des relations plus complexes entre les objets. Ces deux types de relations sont souvent utilisés pour modéliser des situations où un objet est composé d’autres objets ou les contient. Il est important de bien comprendre la différence entre une association simple et une agrégation ou une composition, car cela a un impact sur la manière dont les objets interagissent et sur leur cycle de vie.
Pour distinguer une association d’une agrégation, il faut se poser la question de la dépendance entre les objets concernés. Si les objets peuvent exister indépendamment les uns des autres, il s’agit d’une association. En revanche, si un objet dépend de l’existence d’un autre objet, il s’agit d’une agrégation ou d’une composition. Afin de déterminer s’il est question d’une simple association ou d’une dépendance plus forte, posez-vous ces questions pour déterminer le type de relation entre les objets :
- Si c’est oui à la question « appartient à », C’est une agrégation. Par exemple, un étudiant appartient à une université, mais peut également exister en dehors de celle-ci.
- Si c’est oui à la question « fait partie de », il s’agit d’une agrégation ou d’une composition. Par exemple, un moteur fait partie d’une voiture et s‘il ne peut pas exister indépendamment : c’est une composition.
- Si c’est oui à la question « est composé de », il s’agit là sans doute d’une composition. Par exemple, une équipe de football est composée de joueurs, et ces joueurs ne peuvent pas exister en tant qu’équipe sans l’entité « équipe de football ».
Une autre astuce pour distinguer une association d’une agrégation ou d’une composition est de se demander s’il existe une relation hiérarchique entre les objets impliqués dans la relation. Si les objets ont une relation hiérarchique, mais peuvent indépendamment exister, il s’agit d’une agrégation ; alors que, si les objets ont une relation hiérarchique et que la suppression de l’objet parent entraîne la suppression des objets enfants, cela concerne leune composition. En résumé, posez-vous ces questions pour déterminer le type de relation entre les objets :
- Indépendance : Association
- Relation hiérarchique sans suppression liée : Agrégation
- Relation hiérarchique avec suppression liée : Composition
L’agrégation
L’agrégation est un type d’association qui représente une relation de « conteneur-contenu» entre deux classes d’objets. Dans une agrégation, un objet (le conteneur) est composé d’autres objets (les contenus), mais ces derniers peuvent exister indépendamment du conteneur. L’agrégation est souvent utilisée pour modéliser des situations où un objet est une collection d’autres objets, mais où ces objets peuvent également faire partie d’autres collections ou être utilisés dans d’autres contextes.
Pour implémenter une agrégation, il est nécessaire de définir un attribut dans la classe conteneur qui sera un attribut ou une collection contenant les références aux objets de la classe contenu. Comme pour les associations, il est important de gérer correctement les cas où la collection est vide ou nulle, afin d’éviter des erreurs ou des comportements inattendus dans votre code.
Un exemple d’agrégation pourrait être une classe « Bibliothèque
» et une classe « Livre
». Une bibliothèque contient plusieurs livres, mais un livre peut exister en dehors de la bibliothèque (par exemple, il peut être prêté à un lecteur ou se trouver dans une bibliothèque différente). Dans ce cas, la classe « Bibliothèque
» aurait un attribut de type collection (notamment, une liste) contenant des références aux objets « Livre
». Dans une agrégation, la suppression du conteneur n’entraîne pas la suppression des objets contenus. Par exemple, si une bibliothèque est supprimée, les livres qu’elle contient peuvent toujours exister et être utilisés dans d’autres contextes.
class Livre: def __init__(self, titre): self.titre = titre class Bibliotheque: def __init__(self): self.livres = [] def ajouter_livre(self, livre): self.livres.append(livre) def supprimer_livre(self, livre): self.livres.remove(livre)
class Livre { public: Livre(const std::string& titre) : titre(titre) {} std::string titre; }; class Bibliotheque { public: void ajouter_livre(std::shared_ptr<Livre> livre) { livres.push_back(livre); } void supprimer_livre(std::shared_ptr<Livre> livre) { livres.erase(std::remove(livres.begin(), livres.end(), livre), livres.end()); } private: std::vector<std::shared_ptr<Livre>> livres; };
public class Livre { public string Titre { get; set; } public Livre(string titre) { Titre = titre; } } public class Bibliotheque { public List<Livre> Livres { get; set; } public Bibliotheque() { Livres = new List<Livre>(); } public void AjouterLivre(Livre livre) { Livres.Add(livre); } public void SupprimerLivre(Livre livre) { Livres.Remove(livre); } }
La composition
La composition est un type d’association plus fort que l’agrégation, représentant une relation de “tout-partie” entre deux classes d’objets. Dans une composition, un objet (le tout) est composé d’autres objets (les parties), et ces derniers ne peuvent pas exister indépendamment du tout. La composition est souvent utilisée pour modéliser des situations où un objet est constitué d’autres objets, et où la suppression de l’objet principal entraîne la suppression des objets qui le composent.
Pour implémenter une composition, il est nécessaire de définir un attribut dans la classe du tout qui sera une collection (statique ou dynamique) contenant des références aux objets de la classe partie. Comme pour les agrégations, il est important de gérer correctement les cas où la collection est vide ou nulle, afin d’éviter des erreurs ou des comportements inattendus dans votre code.
Prenons l’exemple d’une classe « Livre » et d’une classe « Page ». Un livre est composé de plusieurs pages, et une page ne peut exister en dehors du livre auquel elle appartient. Dans ce cas, la classe « Livre » aurait un attribut de type collection (par exemple, une liste) contenant des références aux objets « Page ». Contrairement à l’agrégation, dans une composition, la suppression du tout entraîne la suppression des parties. Ainsi, si un livre est supprimé, toutes les pages qui le composent sont également supprimées, car elles ne peuvent pas exister en dehors du livre.
class Chapitre: def __init__(self, titre): self.titre = titre class Livre: def __init__(self): self.chapitres = [] def ajouter_chapitre(self, titre): chapitre = Chapitre(titre) self.chapitres.append(chapitre) def supprimer_chapitre(self, titre): for index, chapitre in enumerate(self.chapitres): if chapitre.titre == titre: self.chapitres.pop(index) break
class Chapitre { public: Chapitre(const std::string& titre) : titre(titre) {} std::string titre; }; class Livre { public: void ajouter_chapitre(const std::string& titre) { auto chapitre = std::make_shared<Chapitre>(titre); chapitres.push_back(chapitre); } void supprimer_chapitre(const std::string& titre) { for (auto it = chapitres.begin(); it != chapitres.end(); ++it) { if ((*it)->titre == titre) { chapitres.erase(it); break; } } } private: std::vector<std::shared_ptr<Chapitre>> chapitres; };
public class Chapitre { public string Titre { get; set; } public Chapitre(string titre) { Titre = titre; } } public class Livre { public List<Chapitre> Chapitres { get; set; } public Livre() { Chapitres = new List<Chapitre>(); } public void AjouterChapitre(string titre) { Chapitre chapitre = new Chapitre(titre); Chapitres.Add(chapitre); } public void SupprimerChapitre(string titre) { for (int i = 0; i < Chapitres.Count; i++) { if (Chapitres[i].Titre == titre) { Chapitres.RemoveAt(i); break; } } } }