Keyboard shortcuts

Touchez ← ou → pour naviguer les chapitres

Touchez S ou / pour chercher dans le livre

Touchez ? pour afficher ce message

Touchez Esc pour masquer ce message

Accueil > Classes et objets >

📚 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é
public et private sont les deux mots-clĂ©s de visibilitĂ© les plus importants en Java. public rend les membres de la classe visibles Ă  l’extĂ©rieur de la classe. private les rend invisibles Ă  l’extĂ©rieur de la classe. Par dĂ©faut (sans mot-clĂ© explicite), la visibilitĂ© est public aux autres classes dans le mĂȘme dossier (package) et private Ă  l’extĂ©rieur du dossier.
Accesseurs et mutateurs
MĂ©thodes public qui donnent accĂšs aux attributs private d’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Ă© private et public.
  • 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 :

  • public qui rend les membres de la classe visibles dans toutes les autres classes
  • private qui 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 Square il fallait dĂ©finir un mutateur spĂ©cialisĂ© pour respecter le concept d’un carrĂ©.

Dans VS Code :

  1. Déclarez chacun des attributs avec le mot-clé private.
  2. Cliquez sur le nom de la classe dans sa signature.
  3. Ici vous avez deux choix :
    1. Faites un clic droit sur le nom et cliquez ensuite sur l’option “Source Action
”
    2. Faites la combinaison Ctrl + . et consultez la section “More Actions”
  4. Selon votre jugement, vous avez les options suivantes :
    1. “Generate Getters and Setters
” qui produit les deux mĂ©thodes pour les attributs que vous sĂ©lectionnez par la suite
    2. “Generate Getters
” qui produit seulement les accesseurs pour les attributs que vous sĂ©lectionnez par la suite
    3. “Generate Setters
” qui produit seulement les mutateurs pour les attributs que vous sĂ©lectionnez par la suite
  5. Une version standard de ces méthodes sera générée, mais il faut :
    1. 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
    2. Modifier le code interne des méthodes pour produire le comportement voulue, au besoin.
    3. 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 :

  • + pour public
  • - pour private

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

Point

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.


  1. Il y a aussi le mot-clĂ© protected qui 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. ↩

  2. Voir la rĂ©fĂ©rence suivante pour les dĂ©tails exacts. ↩

© 2022-2025 David Crowley