Masquage de l’information

Introduction

Un des éléments fondamentaux de la programmation orientée objet, est de permettre de laisser accessible au client de la classe, uniquement ce qui est nécessaire afin d’utiliser les fonctionnalités de la classe. Il est important de comprendre que les classes doivent être robustes et que plus la classe laissa accessible de valeurs ou de fonctionnalités au client, plus elle sera à risque de perdre cette robustesse.

Inversement, une classe qui ne laisse rien accessible à ses clients ne sert à rien. L’utilité d’une classe réside généralement dans les valeurs et les fonctionnalités que cette dernière rend disponibles à ses clients.

On en déduit donc que, comme bien des choses en informatique, il est important d’aller chercher un équilibre entre le nombre de valeurs et de fonctionnalités qui seront accessibles aux clients et la capacité de cette classe de rester robuste et cohérente.

Heureusement, certains mécanismes et standards classiques peuvent être utilisés afin de minimiser la difficulté à garder une classe robuste, tout en maximisant le nombre de valeurs et de fonctionnalités qu’elle laisse accessible aux clients.

Portée des attributs et des méthodes

En programmation orientée objet, les systèmes de masquage de l’information se font généralement sous la forme d’une gestion de portée des attributs et des méthodes. Un langage orienté objet devrait normalement permettre les types de gestion de portée suivante:

    • Portée privée: Accessible à partir de l’objet en cours seulement;
    • Portée publique: Accessible à partir de tous les objets;
    • Portée amis: Accessible à partir d’un sous-ensemble bien défini d’objets.

Java diverge légèrement de la théorie orientée objet sur ce point. En effet, les 3 gestions de portée dans le langage Java se définissent comme suis:

    • Portée privée: Accessible à partir de la classe. N’est pas accessible à partir des enfants de la classe (nous reviendrons sur ce point lorsque nous verrons l’héritage);
      • Il est à noter que d’un point de vue uniquement objet, ce type de porté n’est pas conforme au principe objet;
      • En effet, d’un point de vue strictement théorique, un objet devrait toujours avoir accès aux éléments qui le composent.
        • En d’autres mots, si un Chat est un Animal, Chat devrait avoir accès à tout ce que Animal a accès.
      • La raison pour laquelle Java utilise ce type de gestion de portée est pour s’assurer de garder l’intégrité et la robustesse des classes, même dans un contexte d’héritage;
    • Portée protégé: Accessible à partir de l’objet en cours ainsi qu’à partir de n’importe quelle classe dans le « package » en cours;
      • Sert en même temps de portée privée (tel que vu dans la théorie objet plus haut) que de portée amie;
    • Portée publique: Accessible à partir de tous les objets.

Voici une classe exemple contenant un attribut privé, une méthode protégée et une autre méthode ainsi qu’un constructeur publique:

/**
 * Permet d'effectuer des calculs d'entiers.
 * 
 * @author Louis Marchand
 * @version %I%, %G%
 * 
 * Distribué sous licence MIT.
 */

public class Calcul {
    /**
     * Initialisation de la classe calcul.
     *
     * @param aNombres La liste de nombres à traiter.
     * @see #nombres
     */
    public Calcul(List<Integer> aNombres) {
        changerValeur(aNombres);
    }

    /**
     * La liste de nombre à traiter dans les différentes méthodes de la classe.
     */
    private List<Integer> nombres;

    /**
     * Calcul la somme des nombres.
     *
     * @return La somme des nombres
     * @see #nombres
     */
    public int somme(){
        int lResultat = 0;
        for (Integer element: nombres) {
            lResultat = lResultat + element.intValue();
        }
        return lResultat;
    }
    
    /**
     * Change le contenu de la liste de nombres
     *
     * @param aNombres Les valeurs de la nouvelle liste de nombres.
     * @see #nombres
     */
    protected changerValeur(List<Integer> aNombres){
        nombres = new ArrayList<Integer>(aNombres);
    }
}

Ce qu’il faut comprendre ici, c’est que seulement l’objet en cours peut avoir accès à l’attribut « nombres » et seulement à partir des méthodes de la classe en cours. Les enfants de la classe en cours (voir héritage dans quelques semaines), ainsi que les classes faisant partie du même « package » peuvent modifier les valeurs contenues dans cet attribut « nombre ». Finalement, toutes les classes peuvent utiliser le constructeur ainsi que la méthode « somme » de cette classe.

Protection des données

Afin de garder une cohérence et une rigueur au niveau des informations contenues dans la classe, il est une bonne pratique d’éviter de laisser accès à ces informations par les clients de la classe. En d’autres mots, les attributs des classes doivent être mis « private ».

public class Personne {

    private String nom;

    private String prenom;

    ...

}

Les « getters »

Il va de soi que si l’attribut est « private », le client de la classe ne peut pas y accéder. Afin de permettre tout de même au client d’accéder aux attributs de la classe, nous utilisons des « getters ».

Un « getter » est une méthode qui permet d’accéder aux informations de la classe. Par exemple:

public class Personne {

    private String nom;

    private String prenom;

    public String getNom(){
        return nom;
    }

    public String getPrenom(){
        return prenom;
    }

    ...

}

On voit ici que le client de la classe peut maintenant avoir accès aux attributs « nom » et « prenom ».

Il est à noter qu’un « getter » peut servir à obtenir d’autres informations que seulement la valeur des attributs de la classe. Par exemple:

public class Personne {

    private String nom;

    private String prenom;

    public String getNom(){
        return nom;
    }

    public String getPrenom(){
        return prenom;
    }

    public String getNomComplet(){
        return prenom + " " + nom;
    }

    ...

}

On voit que la méthode « getNomComplet » est un « getter » puisqu’elle permet d’avoir accès à de l’information de la classe, mais cette méthode ne retourne pas directement un attribut. Il s’agit tout de même un « getter » puisque le client de la classe ne peut pas faire la différence.

Les « setters »

De la même manière, puisque l’attribut n’est pas accessible directement par le client de la classe, ce dernier ne peut pas assigner une nouvelle valeur à cet attribut. Donc, pour permettre aux clients de la classe de modifier les informations, il est nécessaire de créer des « setters ».

Un « setter » est une méthode de la classe qui permet de modifier ou d’assigner une (ou plusieurs) information dans la classe. Par exemple:

public class Personne {

    private String nom;

    public String getNom(){
        return nom;
    }

    public void setNom(String aNom) {
        nom = aNom;
    }

    private String prenom;

    public String getPrenom(){
        return prenom;
    }

    public void setPrenom(String aPrenom) {
        prenom = aPrenom;
    }

    public String getNomComplet(){
        return prenom + " " + nom;
    }

    ...

}

On voit ici que le client, en plus de pouvoir accéder à la valeur des attributs « nom » et « prenom », il peut également assigner de nouvelles valeurs à ces attributs.

Un des avantages majeurs d’utiliser des « setters » est de pouvoir valider les valeurs envoyées de la part du client. Par exemple, si je veux m’assurer que le client ne met jamais de valeur NULL dans les attributs « nom » et « prenom », je peux utiliser le code suivant:

public class Personne {

    private String nom;

    public String getNom(){
        return nom;
    }

    public void setNom(String aNom) throws IllegalArgumentException {
        if (aNom != null){
            nom = aNom;
        } else {
            throw new IllegalArgumentException("Le nom ne peut pas être null");
        }
    }

    private String prenom;

    public String getPrenom(){
        return prenom;
    }

    public void setPrenom(String aPrenom) throws IllegalArgumentException {
        if (aPrenom != null){
            prenom = aPrenom;
        } else {
            throw new IllegalArgumentException(
                    "Le prenom ne peut pas être null");
        }
    }

    public String getNomComplet(){
        return prenom + " " + nom;
    }

    ...

}

Prendre en note qu’il est possible d’appeler les « setters » dans un constructeur de classe. Par exemple:

public class Personne {

    private String nom;

    public String getNom(){
        return nom;
    }

    public void setNom(String aNom) throws IllegalArgumentException {
        if (aNom != null){
            nom = aNom;
        } else {
            throw new IllegalArgumentException("Le nom ne peut pas être null");
        }
    }

    private String prenom;

    public String getPrenom(){
        return prenom;
    }

    public void setPrenom(String aPrenom) throws IllegalArgumentException {
        if (aPrenom != null){
            prenom = aPrenom;
        } else {
            throw new IllegalArgumentException(
                    "Le prenom ne peut pas être null");
        }
    }

    public String getNomComplet(){
        return prenom + " " + nom;
    }

    public Personne(String aNom, String aPrenom)
            throws IllegalArgumentException {
        setNom(aNom);
        setPrenom(aPrenom);
    }

}

Il est également à noter qu’il est possible de faire des « setter » afin de placer une valeur dans la classe qui n’est pas placée dans un attribut. Voici un exemple:

public class Personne {

    private String nom;

    public String getNom(){
        return nom;
    }

    public void setNom(String aNom) throws IllegalArgumentException {
        if (aNom != null){
            nom = aNom;
        } else {
            throw new IllegalArgumentException("Le nom ne peut pas être null");
        }
    }

    private String prenom;

    public String getPrenom(){
        return prenom;
    }

    public void setPrenom(String aPrenom) throws IllegalArgumentException {
        if (aPrenom != null){
            prenom = aPrenom;
        } else {
            throw new IllegalArgumentException(
                    "Le prenom ne peut pas être null");
        }
    }

    public String getNomComplet(){
        return prenom + " " + nom;
    }

    public void setNomComplet(String aNom, String aPrenom)
            throws IllegalArgumentException {
        setNom(aNom);
        setPrenom(aPrenom);
    }

    public Personne(String aNom, String aPrenom)
            throws IllegalArgumentException {
        setNomComplet(aNom, aPrenom);
    }

}

Dans cet exemple, on voit le « setter » nommé « setNomComplet » qui prend deux valeurs (le nom et le prénom) et qui les assigne correctement dans la classe. C’est un exemple, mais il est important de comprendre qu’il est possible d’avoir d’autres types de « getter » et de « setter » que seulement pour les représenter les attributs.

Retour

 


Auteur: Louis Marchand
Creative Commons License
Sauf pour les sections spécifiées autrement, ce travail est sous licence Creative Commons Attribution 4.0 International.