Associations non reflexives


Dans cette partie, nous étudierons les associations non reflexives. En terme de programmation objet, cela signifie donc que l'association porte sur des instances de deux classes distinctes. Appelons les Classe1 et Classe2.


Associations un à un

Dans une associations un à un (noté [1-1]) chaque instance de Classe1 est en relation avec au plus une instance de Classe2 et réciproquement.

L'association conduire entre une voiture et une personne est par exemple de ce type

Conduire.jpg, 27kB

Représentation possible

Dans la classe Personne, l'attribut conduit sert de pointeur vers la voiture conduite par cette personne:

public class Personne {
   Voiture conduit;
   ...
} 

Pour une représentation bidirectionnelle, on introduit également un attribut dans la classe Voiture jouant le rôle de pointeur vers le conducteur de la voiture:

 public class Voiture {
   Personne conducteur;
   ....    
}

Exemple de projet utilisant une association [1-1] représentée unidirectionnellement

L'exemple présenté ici gère l'association Conduire en la représentant de manière unidirectionnelle. Vous le trouverez dans le dossier Exemple-Java-ProgObjet2/Conduire1.

Représentation de la classe Personne

Voici la classe Personne en détails:

public class Personne {
   String prenom;   
   Voiture conduit; 
   
   Personne (String p) { 
       prenom = p;
       conduit = null;
   }
   
   void Conduire (Voiture v) { 
       conduit = v;
   }
   
    void ArreterDeConduire ( ) { 
         conduit = null;
    }
}

L'attribut prenom joue de rôle d'identifiant.

L'attribut conduit représente l'association avec la classe Voiture. Il sert donc de pointeur vers l'objet représentant la voiture conduite.

Le constructeur créé une nouvelle personne de prénom donné (le paramètre p) ne conduisant aucune voiture (conduit = null).

La méthode Conduire associe une personne à une voiture (paramètre v).

La méthode ArreterDeConduire supprime l'association d'une personne avec toute voiture.

Représentation de la classe Voiture

La classe Voiture est très sommaire:

public class Voiture {
   String Immat;  
   Voiture (String im) { 
       Immat = im;
   }        
} 

L'attribut Immat représente l'immatriculation de la voiture.

Le constructeur créé une nouvelle voiture d'immatriculation donnée.

A présent, voyons un peu comment ces méthodes sont utilisée depuis l'interface graphique.

Interface graphique
Conduire1.jpg, 34kB

Deux combo box (voir cours sur Swing) permettent de sélectionner une personne donnée ou une voiture donnée.

Le bouton intitulé Conduire associe la personne sélectionnée à la voiture sélectionnée.

Le bouton intitulé Conduit pas n'agit que sur la personne sélectionnée en supprimant toute association éventuelle de cette personne avec une voiture.

La zone de texte sert à afficher l'état des associations entre personnes et voitures après chaque action de l'utilisateur.

Initialisation des données

La classe fenêtre contient deux tables associatives servant à mémoriser l'ensemble des personnes et l'ensemble des voitures.

La première se nomme LesPersonnes. Voici sa déclaration/initialisation:

 TreeMap <String,Personne> LesPersonnes
   = new TreeMap <String,Personne> (); 

La clé de type String représente le prénom de chaque personne.

La deuxième se nomme LesVoitures. Elle est déclarée comme suit:

 TreeMap <String,Voiture> LesVoitures
   = new TreeMap <String,Voiture> ();  

La clé de type String représente le numéro d'immatriculation de chaque voiture.

Les deux tables sont initialisées par deux méthodes nommées CreerLesPersonnes et CreerLesVoitures appelées dans le constructeur de la classe fenêtre. Par conséquent, elles sont initialisés dès le démarrage du projet.

Voici le code de CreerLesPersonnes:

 void CreerLesPersonnes () { 
  String LesPrenoms [] = {"eric","francois", "annick"};
  for (String prenom: LesPrenoms) {
           LesPersonnes.put(prenom, new Personne (prenom));
  }
} 

Le tableau LesPrenoms initialisé explicitement est parcouru grâce à une boucle pour chaque. Dans cette boucle, une nouvelle personne est créée et ajoutée à la table associative LesPersonnes, pour chaque prénom contenu dans le tableau LesPrenoms.

La méthode CreerLesVoitures est basée sur le même principe, en remplacant les prénoms par les numéros d'immatriculation:

void CreerLesVoitures () { 
  String LesImmats [] = {"4017 ZX 67","2020 RT 68", "9018 AT 54"}; 
        for (String immat : LesImmats) {
           LesVoitures.put(immat, new Voiture (immat));
        }
    } 
Initialisation des combo box

Les deux combo box sont remplis à partir des tables associatives LesPersonnes et LesVoitures par deux méthodes nommées ActualiserCB_Personne et ActualiserCB_Voiture, appelées dans le constructeur de la classe fenêtre.

Voici le code de la première:

void ActualiserCB_Personne () { 
  CB_Personne.removeAllItems ();
  for (String prenom : LesPersonnes.keySet()) {
            CB_Personne.addItem(prenom);
        }
} 

Ici, une boucle Pour chaque est utilisée pour parcourir l'ensemble des clés de la table.

La méthode ActualiserCB_Voiture est basée sur le même principe.

Affichage des associations

La méthode AfficherLesConducteurs affiche les associations personne-voiture de chaque personne dans la zone de texte de la manière suivante:

Voici le code de la méthode:

void AfficherLesConducteurs () { 
Voiture v;
es.Effacer(ZT_Conduire);
for (String prenom : LesPersonnes.keySet()) {
     v = LesPersonnes.get(prenom).conduit;
    if (v == null)
      es.Afficher(prenom + " ne conduit pas", ZT_Conduire);
    else
      es.Afficher(prenom + " conduit la voiture immatriculée "
        + v.Immat, ZT_Conduire);         
    }  
} 
La méthode Trouver_Le_Conducteur

Comme nous utilisons ici une représentation unidirectionnelle, le conducteur d'une voiture ne peut pas être retrouvé directement dans les données. Il faut le rechercher en passant en revue toutes les personnes. C'est le rôle de la méthode Trouver_Le_Conducteur:

String Trouver_Le_Conducteur (Voiture v) { 
Personne p; String conducteur=null;
for (String prenom : LesPersonnes.keySet()) { 
    p = LesPersonnes.get(prenom);
    
    if (p.conduit == v) conducteur = prenom;
  }
return conducteur;
} 

Elle retourne le prénom de la personne conduisant une voiture donnée (paramètre v) ou null, si la personne ne conduit aucune voiture. Pour chaque personne, la méthode teste si la voiture conduite par cette personne est la voiture v. Lorsque c'est le cas, le prénom de cette personne est mémorisé dans la variable conducteur.

Le bouton Conduire

Rappelons que ce bouton a pour rôle d'associer la personne sélectionnée à la voiture sélectionnée. Mais cette action ne sera pas systématiquement réalisée. En effet:

Voilà comment nous avons codé ceci:

private void BT_ConduireActionPerformed(...) {                                            
    
String prenom = CB_Personne.getSelectedItem().toString(); 
String immat = CB_Voiture.getSelectedItem().toString();   
   
Personne p = LesPersonnes.get(prenom);
Voiture v = LesVoitures.get(immat);
      
String conducteur = Trouver_Le_Conducteur (v);

if (conducteur != null) {
  es.AfficherMessage("Cette voiture est déjà conduite par "
  + conducteur); return;  }
      
if (p.conduit != null) {
  es.AfficherMessage(p.prenom + 
   " est entrain de conduire la voiture immatriculée "+
    p.conduit.Immat);  return;  }
                 
  p.Conduire(v);
      
  AfficherLesConducteurs ();
}                                           
 

Les deux premières lignes servent à saisir le prénom de la personne et l'immatriculation de la voiture. Nous utilisons, ici les méthodes de la classe JComboBox (voir cours sur Swing), je n'entrerais pas dans les détails.

La référence à l'objet représentant la personne est retrouvée dans la table associative LesPersonnes, via la méthode get puis affectée à la variable p. Même principe, pour retrouver la référence à l'objet représentant la voiture, mais en utilisant cette fois-ci la table associative LesVoitures et en affectant le résultat à la variable v.

Pour voir si la voiture v est déjà conduite, nous recherchons son conducteur par la fonction Trouver_Le_Conducteur. Si c'est le cas (conducteur != null), on affiche un message d'erreur précisant le prénom de ce conducteur et on quitte la procédure.

Nous testons ensuite, si la personne p ne conduit pas déjà une voiture (p.conduit != null). Si c'est le cas, on affiche un message d'erreur précisant l'immatriculation de cette voiture, que l'on obtient par p.conduit.Immat, puis on quitte la procédure.

L'instruction p.Conduire(v); n'est donc exécutée que si l'on a pas quitté la procédure, ou autrement dit lorsque les deux conditions permettant de l'exécuter sont satisfaites.

Finalement, le nouvel état des associations personne-voiture résultant de l'exécution de cette instruction est affiché via la méthode AfficherLesConducteurs.

Le bouton Conduit pas

Une personne ne peut pas arrêter de conduire si elle ne conduit aucune voiture. Il faudra donc tester cette condition avant d'appliquer la méthode ArreterDeConduire à la personne sélectionnée.

private void BT_Conduit_PasActionPerformed(...) {                                               
String prenom = CB_Personne.getSelectedItem().toString(); 
Personne p = LesPersonnes.get(prenom);
      
if (p.conduit == null) {
  es.AfficherMessage(prenom+ "ne conduit aucune voiture ! ");
   return;}
      
 p.ArreterDeConduire ( );
      
 AfficherLesConducteurs ();
}      

Exemple de projet utilisant une association [1-1] représentée bidirectionnellement

L'exemple présenté ici se trouve dans le dossier Exemple-Java-ProgObjet2/Conduire2. C'est en gros le même projet que Conduire1, sauf que l'association Conduire est représentée de manière bidirectionnelle. Nous nous contenterons donc de préciser ici les différences essentielles avec la version précédente.

Représentation de la classe Voiture

La classe Voiture posséde à présent un attribut pour mémoriser le conducteur.

public class Voiture {  
   String Immat;
   Personne conducteur;
   ...
   }        
} 
Représentation de la classe Personne

Par conséquent, les méthodes Conduire et ArreterDeConduire doivent agir sur les deux attributs représentant l'association: celui de la classe Personne (conduit) et celui de la classe Voiture (conducteur):

public class Personne {
  .....
  void Conduire (Voiture v) {    
  conduit = v; 
  v.conducteur = this; }
   
  void ArreterDeConduire ( ) { 
  conduit.conducteur = null;
  conduit = null; }
  ...
}

Dans la méthode Conduire, la deuxième instruction devrait vous paraitre mystérieuse. Que signifie this ?

this en tant que référence à un objet

Dans le premier cours de programmation objet en Java, nous avions vu que this suivi de parenthèses représente un constructeur de la classe (celle à laquelle appartient la méthode dans laquelle figure this).

Dans la méthode Conduire, la signification de this est complètement différente. Ici this représente l'adresse de l'objet auquel on applique la méthode. Dans notre exemple, c'est l'adresse de l'objet de la classe Personne auquel la méthode Conduire est appliquée.

De manière générale, à l'intérieur d'une méthode, le mot clé this (non suivi de parenthèses) représente l'adresse de l'objet auquel cette méthode est appliquée.

Interface graphique
Conduire2.jpg, 50kB

Elle comprend une zone de texte supplémentaire pour afficher les associations dans le sens inverse: pour chaque voiture, la personne qui la conduit.

Initialisation des données et des combo box

Aucun changement par rapport à la version précédente.

Affichage des associations

Il y a à présent une deuxième procédure pour afficher les associations dans la deuxième zone de texte. C'est la procédure AfficherLesVoitures que voici:

void AfficherLesVoitures () {   
Personne p;
es.Effacer(ZT_ConduitPar);

for (String immat : LesVoitures.keySet()) {
  p = LesVoitures.get(immat).conducteur;
  if (p == null)
    es.Afficher("La voiture immaticulée " + immat 
    + " n'est pas conduite", ZT_ConduitPar);
  else
    es.Afficher("La voiture immaticulée " + immat
    + " est conduite par " + p.prenom, ZT_ConduitPar);;           
  }  
} 
Le bouton Conduire

La différence essentielle avec le projet Conduire1, se situe dans l'utilisation de l'attribut conducteur au lieu de la fonction Trouver_Le_Conducteur:

private void BT_ConduireActionPerformed(...) {                                            
...     
  if (v.conducteur != null) {
   es.AfficherMessage("La voiture immatriculée "+ v.Immat +
    " est déjà conduite par "+ v.conducteur.prenom);
    return;
  }
  ...
      
  AfficherLesConducteurs ();
  AfficherLesVoitures ();
}  
Le bouton Conduit pas

Pas de différences essentielles.

Associations un à plusieurs

Dans une association un à plusieurs (notée [1-N]) une instance de Classe 1 peut être en relation avec plusieurs instances de Classe2, mais chaque instance de Classe2 est en relation avec au plus une instance de Classe1.

L'association passagers entre une une voiture et une personne est par exemple de ce type. Une voiture peut avoir plusieurs passagers. Par contre, une personne ne peut être le passager que d'au plus une voiture.

Asso-Passager.jpg, 35kB

Représentation possible

Nous proposons ici une représentation bidirectionnelle de cette association. Dans la classe Personne, l'attribut DansVoiture pointe vers la voiture dans laquelle se trouve la personne en question:

public class Personne {
   String prenom;
   Voiture DansVoiture; 
   ...  
}

En supposant pour simplifier que le prénom identifie une personne de manière unique, on peut utiliser une table associative pour représenter les passagers d'une voiture:

public class Voiture {
   TreeMap <String,Personne> LesPassagers;
   ...
} 

Dans cette table, les clés sont donc les prénoms des personnes présentes dans la voiture.

Exemple de projet

Voir le corrigé du projet Passagers1, pour un exemple traitant l'association de manière unidirectionnelle, et le corrigé du projet Passagers2, pour un exemple traitant l'association de manière bidirectionnelle.

Associations plusieurs à plusieurs

Dans un association plusieurs à plusieurs (notée [N-N]) une instance de Classe 1 peut être en relation avec plusieurs instances de Classe2 et inversement.

Comme exemple, considérons cette fois-ci les voitures qu'une personne a conduit au cours de sa vie. Il s'agit bien d'une associations [N-N] car une personne peut avoir conduit plusieurs voitures et inversement, une voiture donnée peut avoir été conduite par plusieurs personnes.

Asso-A-Conduit.jpg, 44kB

Représentation possible

L'ensemble des voitures qu'a conduit une personne est représenté par une table associative, dont les clés sont les numéros d'immatriculation des voitures (cf attribut Immat de la classe Voiture):

public class Personne {
   String prenom;
   TreeMap <String,Voiture> A_Conduit;
   ... 

Pour obtenir une représentation bidirectionnelle, on représente de même l'ensemble des personnes ayant conduit une voiture donnée par une table associative:

public class Voiture {
   String Immat;
   TreeMap <String,Personne> A_Ete_Conduite_Par;
      
} 

ici les clés sont les prénoms des personnes ayant conduit la voiture en question.

Exemple de projet

Voir le corrigé du projet A_Conduit1, pour un exemple traitant l'association de manière unidirectionnelle, et le corrigé du projet A_Conduit2, pour un exemple traitant l'association de manière bidirectionnelle.