đ Masquer les dĂ©tails internes
Survol et attentes
Un des principes de la programmation orientĂ©e objet et de protĂ©ger lâaccĂšs aux attributs. On ne lis pas directement leurs valeurs et on ne les modifie pas directement non plus. PlutĂŽt on Ă©crit des mĂ©thodes qui retournent la valeur des attributs et des mĂ©thodes qui modifient la valeur des attributs. Ainsi, les attributs sont cachĂ©s via une interface (les mĂ©thodes); cette couche forme la capsule protectrice autour des donnĂ©es.
Définitions
- Masquage de lâinformation
- Selon certaines dĂ©finitions de lâencapsulation, lâĂ©lĂ©ment clĂ© nâest pas nĂ©cessairement lâemballage des donnĂ©es avec les mĂ©thodes associĂ©es mais le fait de cacher les dĂ©tails dâimplĂ©mentation aux Ă©lĂ©ments externes Ă la classe. Cela permet de changer lâimplĂ©mentation (le code dans la classe) sans changer lâinterface (les appels de mĂ©thodes de lâextĂ©rieure de la classe). Un effet important de lâencapsulation est que les attributs ne sont pas accessibles directement de lâextĂ©rieur de la classe.
- Mot-clés de visibilité
publicetprivatesont les deux mots-clĂ©s de visibilitĂ© les plus importants en Java.publicrend les membres de la classe visibles Ă lâextĂ©rieur de la classe.privateles rend invisibles Ă lâextĂ©rieur de la classe. Par dĂ©faut (sans mot-clĂ© explicite), la visibilitĂ© estpublicaux autres classes dans le mĂȘme dossier (package) etprivateĂ lâextĂ©rieur du dossier.- Accesseurs et mutateurs
- Méthodes
publicqui donnent accĂšs aux attributsprivatedâune classe. Les accesseurs (get[Attribut]) retournent la valeur de lâattribut. Les mutateurs (set[Attribut]) modifient la valeur de lâattribut selon la valeur du paramĂštre. Autre que simplement assigner ou retourner la valeur, ces mĂ©thodes peuvent aussi appliquer diffĂ©rents traitements (validation de la valeur, mise Ă jour dâautres attributs, etc.). Cela fait partie des âdĂ©tails dâimplĂ©mentationâ qui sont cachĂ©s Ă lâextĂ©rieur de la classe mais qui permettent Ă la classe de fonctionner comme prĂ©vu.
Objectifs dâapprentissage
Ă la fin de cette leçon vous devrez ĂȘtre en mesure de :
- Expliquer le concept dâencapsulation.
- Définir et distinguer les mot-clé
privateetpublic. - Définir et distinguer les termes accesseur et mutateur.
CritĂšres de succĂšs
- Je suis capable dâencapsuler mes propres objets avec lâaide de lâEDI.
- Je suis capable de produire des mutateurs sur mesure pour respecter le comportement attendu de mon objet.
ProblÚmes potentiels sans encapsulation (masquage) des données
Voici un exemple pour illustrer lâutilitĂ© du masquage de lâinformation. Il prĂ©sente un cas oĂč lâabsence de ce type dâencapsulation peut mener Ă un objet qui ne respecte plus les rĂšgles de sa dĂ©finition (un carrĂ© qui nâest plus un carrĂ©).
/**
* Classe pour représenter un carré
* Version sans encapsulation
*/
class Square {
double side;
double area;
double perimeter;
Square(double side) {
update(side);
}
Square() {
this(1); // valeur par défaut
}
/**
* Méthode pour valider et mettre à jour les attributs
* @param side la longueur de cÎté
*/
void update(double side) {
if (side < 0) {
System.out.println("Longueur invalide");
return;
}
this.side = side;
// area et perimeter dépendent de side
this.area = side*side;
this.perimeter = 4*side;
}
@Override
public String toString() {
return String.format("CÎté = %.2f, A = %.2f, P = %.2f",
side, area, perimeter);
}
}
Cette classe a trois attributs mais deux, area et perimeter, dépendent de la valeur du dernier, side.
Regardons maintenant la classe pilote qui inclut le code valide suivant :
class Main {
public static void main(String[] args) {
Square sq = new Square(2);
System.out.println(sq);
sq.area = -27; // instruction valide qui brise le concept de carré
System.out.println(sq);
}
}
qui produit la sortie suivante :
CÎté = 2.00, A = 4.00, P = 8.00
CÎté = 2.00, A = -27.00, P = 8.00
Cette sortie montre un carrĂ© qui ne respecte plus la dĂ©finition dâun carrĂ©. Notre objet nâest plus un modĂšle valide pour ce quâil essaye de reprĂ©senter. Le problĂšme est la 3e ligne dans main oĂč on modifie directement lâattribut area.
Encapsulation pour protéger les données
Lâencapsulation prĂ©vient ce genre dâincohĂ©rence en nous permettant de spĂ©cifier la visibilitĂ© (voire lâaccĂšs) des attributs et des mĂ©thodes. Pour le moment, seulement deux mots-clĂ©s sont importants 1 :
publicqui rend les membres de la classe visibles dans toutes les autres classesprivatequi rend les membres de la classe visibles seulement Ă lâintĂ©rieur de la classe (pour les autres membres)
Lâabsence dâun mot-clĂ© de visibilitĂ© donne au membre la visibilitĂ© par dĂ©faut qui est public.2 Donc, dans lâexemple de la classe Square ci-dessus, la classe et tout ses membres sont public.
Attributs privés -> méthodes accesseurs et méthodes mutateurs
Une classe encapsulée déclare ses données (les attributs) private et inclut des méthodes public pour lire les données et pour modifier les données.
Ces mĂ©thodes sâappellent :
- accesseurs (âgettersâ en anglais) pour retourner la valeur dâun attribut privĂ©;
Les accesseurs sont du mĂȘme type que la donnĂ©e demandĂ©e et ne prennent aucun paramĂštre.
- mutateurs (âsettersâ en anglais) pour modifier la valeur dâun attribut privĂ©.
Les mutateurs sont de type void et prennent un paramĂštre du mĂȘme type que la donnĂ©e Ă modifier.
ModĂšle de carrĂ© prĂ©servĂ© dans la classe Square avec lâencapsulation
Voici une version encapsulée de la classe Square :
/**
* Classe pour représenter un carré
* Version appliquant encapsulation
*/
class Square {
// attributs masqués à l'extérieur de la classe
private double side;
private double area;
private double perimeter;
Square(double side) {
update(side);
}
Square() {
this(1); // valeur par défaut
}
// update() est le seul mutateur pour la classe puisqu'il
// modifie tous les attributs de maniÚre contrÎlée.
/**
* Méthode pour valider et mettre à jour les valeurs.
* @param side la longueur de cÎté
*/
public void update(double side) {
if (side < 0) {
System.out.println("Longueur invalide");
return;
}
this.side = side;
// area et perimeter dépendent de side
this.area = side*side;
this.perimeter = 4*side;
}
// trois accesseurs pour lire les valeurs à l'extérieur de la classe
public double getSide() {
return this.side;
}
public double getArea() {
return this.area;
}
public double getPerimeter() {
return this.perimeter;
}
@Override
public String toString() {
return String.format("CÎté = %.2f, A = %.2f, P = %.2f",
side, area, perimeter);
}
}
Maintenant la ligne
sq.area = -27;
dans le pilote produit une erreur parce que lâaccĂšs Ă area est rendu private. On ne peut plus accidentellement (ou intentionellement) briser notre objet dans son objectif de reprĂ©senter un carrĂ©.
Accesseurs et mutateurs idiomatiques / formulaĂŻques
Dans lâexemple de la classe Square, le mutateur Ă©tait taillĂ© sur mesure pour lâobjet, ce qui est souvent requis. Mais il y a aussi plein de cas oĂč des mutateurs plus gĂ©nĂ©riques sont suffisants. La classe Point ci-dessous est un bon exemple.
class Point {
private int x;
private int y;
public void setX(int x){
this.x = x;
}
public void setY(int y){
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
Dans cette classe partiellement implĂ©mentĂ©e (il manque : constructeurs, toString, etc.) mais qui est entiĂšrement encapsulĂ©e, on voit que les accesseurs (get[Attribut]) et les mutateurs (set[Attribut]) sont presque identiques. Ils suivent lâidiome pour ces types de mĂ©thodes :
Idiome pour les accesseurs
Une mĂ©thode nommĂ© âgetâ + le nom de lâattribut qui est sans paramĂštre et qui retourne la valeur de lâattribut.
public [type] get[Attribut](){
return [attribut];
}
comme
public int getX() {
return x;
}
Idiome pour les mutateurs
Une mĂ©thode nommĂ© âsetâ + le nom de lâattribut qui prend un paramĂštre du mĂȘme type que lâattribut et qui assigne la valeur du paramĂštre Ă lâattribut.
public void set[Attribut]([type] [attribut]){
this.[attribut] = [attribut];
}
comme
public void setX(int x){
this.x = x;
}
GĂ©nĂ©rer les accesseurs et les mutateurs avec les outils de lâEDI
En fait, le code pour les mutateurs et les accesseurs dans lâexemple de la classe Point a Ă©tĂ© copiĂ©-collĂ©-modifiĂ© manuellement, ce qui est une tĂąche rĂ©pĂ©titive qui ne nĂ©cessite pas beaucoup dâengagement intellectuel de la part du dĂ©veloppeur et qui peut mener Ă des erreurs dâinattention, surtout quand il y a plusieurs attributs dans la classe.
La bonne nouvelle et que si les mutateurs et les accesseurs sont du genre répétitifs comme dans la classe Point, les outils de votre EDI (VS Code, Eclipse, etc.) peuvent les générer automatiquement.
Câest quand mĂȘme important de sâassurer que ces mĂ©thodes reflĂštent rĂ©ellement le comportement attendu et de modifier ou de remplacer les mĂ©thodes au besoin. Par exemple, dans la classe
Squareil fallait dĂ©finir un mutateur spĂ©cialisĂ© pour respecter le concept dâun carrĂ©.
Dans VS Code :
- Déclarez chacun des attributs avec le mot-clé
private. - Cliquez sur le nom de la classe dans sa signature.
- Ici vous avez deux choix :
- Faites un clic droit sur le nom et cliquez ensuite sur lâoption âSource ActionâŠâ
- Faites la combinaison
Ctrl + .et consultez la section âMore Actionsâ
- Selon votre jugement, vous avez les options suivantes :
- âGenerate Getters and SettersâŠâ qui produit les deux mĂ©thodes pour les attributs que vous sĂ©lectionnez par la suite
- âGenerate GettersâŠâ qui produit seulement les accesseurs pour les attributs que vous sĂ©lectionnez par la suite
- âGenerate SettersâŠâ qui produit seulement les mutateurs pour les attributs que vous sĂ©lectionnez par la suite
- Une version standard de ces méthodes sera générée, mais il faut :
- Les dĂ©placer afin de garder les dĂ©clarations dâattributs ensemble au dĂ©but de la classe et les dĂ©finitions de mĂ©thodes plus bas
- Modifier le code interne des méthodes pour produire le comportement voulue, au besoin.
- Probablement reformatter le code (clic droit -> âFormat Documentâ) afin de corriger les indentations brisĂ©es suite aux dĂ©placements de code.
Indiquer la visibilité des membres dans un diagramme de classe UML
Dans un diagramme de classe UML, on indique la visibilité des membres avec des symboles spécifiques :
+pourpublic-pourprivate
Voici les diagrammes de classe UML pour lâexemple Point ci-dessus.
@startuml Point
!theme toy
class Point {
- x : int
- y : int
+ setX(int) : void
+ setY(int) : void
+ getX() : int
+ getY() : int
}
@enduml
Exercices
Changez la visibilité des tous les attributs de votre objet et générez des accesseurs et mutateurs appropriés.
Testez ces nouvelles mĂ©thodes dans la classe pilote. VĂ©rifiez que lâaccĂšs direct Ă chacun des attributs via la classe pilote produit une erreur.
-
Il y a aussi le mot-clé
protectedqui sâapplique lorsquâon a des classes qui hĂ©ritent explicitement le contenu dâune classe parent. On en discutera lors de la leçon sur lâhĂ©ritage dans une prochaine unitĂ© sur lâorientĂ© objet. â© -
Voir la rĂ©fĂ©rence suivante pour les dĂ©tails exacts. â©