Les relations entre les classes
Présentation
Dans le chapitre précédent, les classes créées n’ont aucune relation avec d’autres classes. Ce chapitre présente comment les classes peuvent être mises en relation les unes avec les autres. Il existe trois types de relations entre deux classes. Tout d’abord, il y a l’utilisation d’une classe par une autre. Ensuite, il y a l’association d’une classe avec une autre. Enfin, il y a l’héritage d’une classe par une autre.
L’utilisation d’une classe par une autre
Une classe utilise une autre classe dans différentes situations :
-
Lorsque la première possède une méthode prenant en paramètre une instance de l’autre.
-
Lorsqu’une des méthodes de la première retourne une instance de la seconde.
-
Lorsque la première fait appel à une méthode, à une constante ou à un constructeur de la seconde.
En résumé, dès que le nom d’une classe apparaît dans une autre classe, cette dernière utilise la première.
Par exemple, la classe GrilleDeJeu présentée ci-dessous utilise la classe Bateau :
Classe GrilleDeJeu
Constante BOOM : entier <- 2
Constante INCONNU : entier <- 3
Constante PLOUF : entier <- 4
Constante LARGEUR : entier <- 10
Constante HAUTEUR : entier <- 10
Attribut plateauDeJeu : entier[HAUTEUR][LARGEUR]
Méthodes
Constructeur()
Variable i, j : entier
Début
Pour j <- 0 à HAUTEUR - 1
Pour i <- 0 à LARGEUR - 1
instance.plateauDeJeu[j][i] <- INCONNU
FPour
FPour
Fin
Procédure afficher()
Variable i, j : entier
Début
Pour j <-...
Les associations
L’association d’une classe vis-à-vis d’une autre est une relation plus forte que la simple utilisation. Non seulement la classe utilise l’autre classe, mais elle stocke également une ou plusieurs instances de celle-ci au sein d’un ou de plusieurs de ses attributs. Dans le chapitre précédent, des variables de type d’une classe et référençant une instance de celle-ci étaient utilisées. Il y avait également des classes ayant des attributs de type entier, réel, booléen, caractère ou texte, ou bien encore un tableau contenant des valeurs de l’un de ces types. Dans ce paragraphe est présentée la création d’attributs de type d’une classe ou d’un tableau contenant des instances de classe : cela correspondant à la déclaration d’une association.
L’exemple suivant illustre ce concept d’association :
Dans la classe Joueur, il y a six attributs. Trois d’entre eux utilisent des types qui ne sont pas des classes (un de type texte et deux de type entier). Les trois autres attributs ont pour type des classes, ce sont donc trois associations.
Classe Joueur
Attribut nom : texte
Attribut champDeTir : GrilleDeJeu <- nouveau GrilleDeJeu()
Attribut bateaux : Bateau[5]
Attribut nbBateaux : entier <- 0
Attribut nbBateauxCoules : entier <- 0
Attribut adversaire : Joueur
...
FClasse
Tout d’abord, l’attribut champDeTir est de type Grille. À une instance de Joueur est donc associée une instance de GrilleDeJeu, c’est-à-dire la zone dans laquelle il va effectuer ses tirs avec pour objectif de couler les bateaux de son adversaire. Cette association ne va que dans un seul sens : une instance de Joueur connaît sa grille de jeu grâce à cet attribut, mais par contre, la grille de jeu ne sait pas à quel joueur elle est associée. Cette association est dite navigable que dans un seul sens (c’est bien le terme officiel, ce n’est pas pour rester dans la thématique de la bataille navale…). C’est donc une association unidirectionnelle.
Ensuite, l’attribut bateaux est un tableau dont les éléments...
L’héritage
1. La notion d’héritage
La troisième relation qu’une classe peut posséder vis-à-vis d’une autre est l’héritage. L’objectif de l’héritage est de définir une classe non pas à partir de zéro, mais à partir d’une autre classe. La classe servant de base est nommée classe parent et celle créée en se basant sur celle-ci classe enfant ou fille.
Grâce à l’héritage, la classe fille aura déjà tous les attributs et toutes les méthodes définies dans la classe parent. Ces éléments sont obtenus en héritage, un peu comme les enfants héritent génétiquement de leurs parents : un enfant a par exemple la même apparence que son père et les mêmes yeux que sa mère. La grande différence par rapport aux humains, c’est que les classes n’ont qu’un parent.
En plus de ces éléments hérités, il sera possible d’ajouter des attributs et des méthodes supplémentaires.
Un enfant peut avoir des éléments qui lui sont propres, par exemple une cicatrice sur le front.
Pour qu’une classe hérite d’une autre classe, il faut que la classe fille soit un cas particulier de la classe parent. Pour s’assurer de cela, n’hésitez pas à vous poser la question suivante : X est-il un cas particulier de Y ? Si oui, alors la classe X peut hériter de la classe Y. Par exemple : un vélo est-il un cas particulier de véhicule ? Oui, alors la classe Velo peut hériter d’une classe Vehicule. Par contre, à la question : une trottinette est-elle un cas particulier de vélo, nous répondons non. La classe Trottinette n’héritera donc pas de la classe Velo.
Les classes peuvent donc être vues comme une hiérarchie où les catégories les plus générales seraient les classes au sommet de cette hiérarchie, et de ces classes hériteraient des sous-catégories plus spécifiques.
Il faut faire tout de même attention à ne pas abuser de l’héritage. Un trop grand nombre de niveaux d’héritage nuit à la performance du programme et à...
Les classes internes en Java
En Java, il est possible de définir une classe au sein d’une autre classe. Bien souvent, il s’agit d’une classe privée qui est utilisée uniquement au sein de cette classe. La classe interne se nomme nested class en anglais.
1. La définition
Syntaxe :
visibilité class NomDeLaClasseEnglobante {
...
private [static] class NomDeLaClasseInterne {
...
}
}
La classe interne est définie au même niveau qu’un attribut ou une méthode de la classe englobante. Si le mot-clé static est positionné, alors il s’agit d’une classe utilitaire interne qui ne pourra contenir que des méthodes de classe. Ce cas de figure est plutôt rare. Une classe interne est généralement créée pour que la classe englobante puisse créer une ou plusieurs instances, mais que ces instances ne soient utilisées qu’au sein de cette classe englobante.
Une classe interne est définie comme habituellement, à la différence près de son emplacement inhabituel au sein d’une classe et de sa visibilité private. Elle peut définir comme toute autre classe, ses attributs d’instance ou de classe, ses constantes, ses constructeurs et ses méthodes d’instance ou de classe.
Par exemple, il est possible de définir une classe Commande définissant en son sein une classe Article. Cette classe Article n’est alors connue et utilisée que par la classe Commande. Il ne doit donc avoir aucun constructeur ou méthode publique qui prend un paramètre de type Article ou qui retourne un type Article. En revanche, tout ceci est possible si le constructeur ou la méthode est privé.
Exemple :
import java.time.LocalDateTime; ...
La création d’instances par méthode de classe en Java
Les méthodes de classe n’ont pas besoin d’une instance pour être appelées. Elles sont par contre situées au sein de la classe et ont donc accès aux éléments privés (à condition que ce ne soient pas des éléments d’instance). En particulier, ces méthodes ont accès à un constructeur ayant une visibilité privée. Une méthode de classe peut donc créer une instance en y faisant appel et retourner cette instance. Cette technique se nomme une fabrique d’instance par méthode de classe.
1. L’encapsulation des constructeurs
À l’instar des attributs qui sont protégés en application du principe d’encapsulation, l’accès aux constructeurs peut également être contrôlé. Pour mettre cela en place, il faut que la classe ne possède que des constructeurs privés, ou protégés si des classes en héritent. Si la classe ne possède aucun constructeur explicite, n’oubliez pas qu’un constructeur par défaut est fourni et que celui-ci est public. Dans ce cas, vous pouvez créer explicitement un constructeur avec une visibilité privée ou protégée ne prenant pas d’argument et n’ayant aucune instruction, ainsi le constructeur par défaut n’est pas ajouté.
Depuis l’extérieur de la classe, il n’est plus possible de faire appel à un constructeur de celle-ci. Le seul moyen pour créer une instance est donc de faire appel à une méthode de classe retournant une instance de celle-ci. Cette encapsulation permet de n’appeler le constructeur qu’une fois que toutes les règles métier ont été vérifiées. La création d’une instance est un processus coûteux, ainsi, le constructeur n’est appelé que si réellement une instance doit être créée (lorsqu’une règle métier est vérifiée au sein d’un constructeur public et n’est pas satisfaite, il est possible d’interrompre le traitement, mais "le mal est déjà fait !"). De plus, afin de produire un code efficace...
Exercices
1. La bataille de dés
Prérequis : Exercice 1 du chapitre La programmation orientée objet
Deux joueurs s’affrontent dans une bataille de dés : chaque joueur possède un dé à six faces et dix jetons.
Créer une classe JoueurBataille ayant comme attributs d’instance le nombre de jetons, un dé (une instance de la classe De) et le nom du joueur. Ajouter au sein de cette classe les autres éléments nécessaires. Créer un algorithme principal utilisant cette classe.
Exemple d’exécution :
Nom du joueur ? |
Jeanne |
Nom du joueur ? |
Léonie |
Jeanne a fait un 4 |
Léonie a fait un 1 |
Jeanne remporte |
Score : Jeanne 11 - Léonie 9 |
Jeanne a fait un 1 |
Léonie a fait un 5 |
Léonie remporte |
Score : Jeanne 10 - Léonie 10 |
Jeanne a fait un 5 |
Léonie a fait un 2 |
Jeanne remporte |
Score : Jeanne 11 - Léonie 9 |
Jeanne a fait un 1 |
Léonie a fait un 6 |
Léonie remporte |
Score : Jeanne 10 - Léonie 10 |
Jeanne a fait un 3 |
Léonie a fait un 6 |
Léonie remporte |
Score : Jeanne 9 - Léonie 11 |
Jeanne a fait un 5 |
Léonie a fait un 6 |
Léonie remporte |
Score : Jeanne 8 - Léonie 12 |
Jeanne a fait un 2 |
Léonie a fait un 2 |
égalité |
Score : Jeanne 8 - Léonie 12 |
Jeanne a fait un 6 |
Léonie a fait un 6 |
égalité |
Score : Jeanne 8 - Léonie 12 |
Jeanne a fait un 3 |
Léonie a fait un 6 |
Léonie remporte |
Score : Jeanne 7 - Léonie 13 |
Jeanne a fait un 1 |
Léonie a fait un 5 |
Léonie remporte |
Score : Jeanne 6 - Léonie 14 |
Jeanne a fait un 2 |
Léonie a fait un 5 |
Léonie remporte |
Score : Jeanne 5 - Léonie 15 |
Jeanne a fait un 3 |
Léonie a fait un 5 |
Léonie remporte |
Score : Jeanne 4 - Léonie 16 |
Jeanne a fait un 2 |
Léonie a fait un 3 |
Léonie remporte |
Score : Jeanne 3 - Léonie 17 |
Léonie a fait un 5 |
Léonie remporte |
Score : Jeanne 2 - Léonie 18 |
Jeanne a fait un 1 |
Léonie a fait un 6 |
Léonie remporte |
Score : Jeanne 1 - Léonie 19 |
Jeanne a fait un 1 |
Léonie a fait un 2 |
Léonie remporte |
Score : Jeanne 0 - Léonie 20 |
Léonie gagne ! |
2. Les clients (version 2)
Prérequis : Exercice 2 du chapitre La programmation orientée objet
Les clients créés au chapitre précédent...
Solutions des exercices
1. La bataille de dés
Classe JoueurBataille
Attribut nbJetons : entier
Attribut de : De
Attribut nom : texte
Méthodes
Constructeur()
Début
instance.nom <- saisir("nom du joueur ?")
instance.nbJetons <- 10
instance.de <- nouveau De()
Fin
Fonction tourDeJeu() Retourne entier
Début
écrire(instance.nom & " a fait un " & instance.de.lancer())
Retourner instance.de.getFaceTiree()
Fin
Fonction perdUnJeton() Retourne booléen
Début
instance.nbJetons <- instance.nbJetons - 1
Retourner instance.nbJetons = 0
Fin
Procédure gagneUnJeton()
Début
instance.nbJetons <- instance.nbJetons + 1
Fin
Fonction getNom() Retourne texte
Début
Retourner instance.nom
Fin
Fonction getNbJetons() Retourne entier
Début
Retourner instance.nbJetons
Fin
FClasse
Algo BatailleDeDes
Variable j1 : JoueurBataille <- nouveau JoueurBataille()
Variable j2 : JoueurBataille <- nouveau JoueurBataille()
Variable fin : booléen
Variable score1, score2 : entier
Début
Répéter
score1 <- j1.tourDeJeu()
score2 <- j2.tourDeJeu()
Si score1 > score2 Alors
écrire(j1.getNom() & " remporte")
j1.gagneUnJeton()
fin <- j2.perdUnJeton()
Sinon
Si score1 < score2 Alors
écrire(j2.getNom() & " remporte")
j2.gagneUnJeton()
fin <- j1.perdUnJeton()
Sinon
écrire("égalité") ...