Les associations


Jusqu'ici nous avons considéré les objets de manière isolée, sans tenir compte des informations qui pouvaient les relier. Or, dans les logiciels un tant soit peu complexes, les objets sont rarement indépendants.

Exemple 1: l'association emprunter

Prenons un exemple très simple: un logiciel de gestion d'une bibliothèque utilisant une classe lecteur et une classe livre. Il serait intéressant dans ce cas de pouvoir retrouver rapidement les livres empruntés par un lecteur. Nous avons ici un exemple d'association entre deux classes que l'on pourrait nommer l'association emprunter.

Asso-Emprunter.jpg, 33kB

Cette association est du type un à plusieurs, car chaque objet de la classe lecteur peut être associé à plusieurs livres. Par contre, un livre n'est associé qu'à un seul lecteur : celui qui l'a umprunté. D'autres associations peuvent être du type un à un ou plusieurs à plusieurs. Nous en verrons des exemples ultérieurement.

Exemple 2: l'association connaitre

Dans l'exemple precédent, nous avons considéré une association entre deux classes distinctes: la classe lecteur et la classe livre. Mais, on peut très bien s'intéresser à des associations entre objets de la même classe.

L'association connaitre entre deux personnes est de ce type.

Asso-Connaitre.jpg, 41kB

Lorsqu'une association concerne des objets d'une même classe, nous dirons qu'elle est réflexive.

Notez que les associations reflexives peuvent être symétriques, c'est à dire que les deux objets en relation jouent le même rôle dans l'association. C'est par exemple le cas de l'association connaitre: si une personne A connait une personne B, alors la personne B connait forcément la personne A.

Objectif

Il existe ainsi différentes catégories d'association et notre objectif dans cette partie du cours est de les énumérer et de voir comment les représenter. La manière de gèrer les associations ne sera pas détaillée ici, car je pense qu'il suffit d'un peu de logique. Par contre, je vous conseille vivement de faire les exercices sur les associations (vous y trouverez un exercice pour chaque type d'association traitée dans le cours). Comme dans beaucoup de domaines, la programmation est un art que l'on comprend avant tout en le pratiquant !

Représentations possibles

Principes généraux

Toutes les catégories d'association que nous allons voir peuvent se représenter en définissant des attributs particuliers destinés à mémoriser pour un objet donné, l'objet ou l'ensemble des objets de l'autre classe qui lui sont associés.

Lorsque l'attribut en question ne doit mémoriser qu'au plus un objet, il suffit de déclarer cet attribut comme un pointeur vers un objet de l'autre classe (ou de la même si l'association est réflexive), en supposant que cet attribut prend la valeur null lorsqu'il n'y a aucun objet en relation.

Par contre, lorsqu'il doit mémoriser un ensemble d'objets, plusieurs choix de représentation sont envisageables: tableaux, liste génériques , ensembles génériques, tables associatives, etc ...

Voyons ceci plus en détails en reprenant l'exemple de l'association emprunter.


Représentation de l'ensemble des objets en relation

Un lecteur est associé à un ensemble de livres. Nous allons donc déclarer dans la classe lecteur un attribut servant à mémoriser l'ensemble des livres empruntés par un lecteur donné. Appelons le livres_empruntes.

Une première idée serait de représenter cet attribut par un tableau d'objets de la classe livre:

class lecteur {
   livre livres_empruntes = new livre[MAX_LIVRE];
   .....
}

MAX_LIVRE représente le nombre maximum de livres empruntables par un lecteur.

Une deuxième solution serait une liste générique:

class lecteur {
   LinkedList <livre > livres_empruntes =  
      new LinkedList <livre> ( );
   .....
}

Comme nous l'avons déjà vu dans la partie du cours consacrée aux pointeurs et listes, cette représentation est plus efficace que les tableaux aussi bien au niveau de la place mémoire utilisée qu'au niveau de la facilité de mise à jour.

Par contre, une liste n'est réellement intéressante que lorsqu'il existe un ordre sur les objets en relation (un ordre temporel par exemple). Sinon, la représentation par un ensemble est plus adaptée:

class lecteur {
   TreeSet <livre> livres_empruntes = new TreeSet <livre> ( ) ;
   .....
}

Enfin, lorque les objets en relation possèdent un identifiant, la représentation par une table associative me semble plus indiquée (voir la section sur la représentation des ensembles par des tableaux associatifs).

Supposons par exemple que chaque livre soit identifié de manière unique par son numéro ISBN, attribut de type String de la classe livre:

class livre {
  String numero_ISBN;
  .....
}

On pourrait alors représenter l'association emprunter par

class lecteur {
   TreeMap <String;livre> livres_empruntes
    = new TreeMap <String;livre>( );
   .....
}

en supposant que les clés de la table sont les numéros ISBN des livres empruntés.


Représentation bidirectionnelle

Pour être plus efficace dans les recherches d'informations, il pourrait également être judicieux de représenter l'association de manière bidirectionnelle. En effet, la représentation précédente permet de trouver facilement les livres empruntés par un lecteur, mais plus difficilement, le lecteur ayant emprunté un livre donné. C'est une représentation unidirectionnelle. Pour représenter l'association de manière bidirectionnelle, il suffit de rajouter un attribut emprunte_par dans la classe livre jouant le rôle de pointeur vers le lecteur associé:

class livre {
  String numero_ISBN;
  lecteur emprunte_par;
  .....
} 

Relation avec les bases de données

Si vous avez des connaissances en base de données et dans la méthode d'analyse merise, ce paragraphe pourra vous intéresser, car les associations permettent de représenter une base de données relationnelle en mémoire.

Chaque entité du modèle conceptuel est représentée par une classe. Dans la base de données cela correspond à une table. En mémoire, chaque enregistrement (ou ligne) de cette table sera un objet de la classe correspondante.

Les associations (au sens merise) entre entités sont représentées dans la base de données par des colonnes (ou champs) particulières (les clés étrangères) ou par des tables. En mémoire elles seront représentées par des pointeurs ou des tables associatives mémorisées dans des attributs particuliers des classes représentant chaque entité.