Adloun

Encapsulation, membres de classe et paquets

Cours · programmation Java (école d'ingénieur), chapitre 8 · école d'ingénieur, informatique · le début du chapitre, la suite sur Adloun

Au chapitre précédent, n'importe quelle partie du programme pouvait écrire c.solde = -1000000; sur un compte bancaire. Rien n'empêchait de créer une fraction de dénominateur nul, un point de couleur « bleuu », une date au 31 avril. Une classe dont tout le monde peut modifier les attributs n'est qu'un regroupement de variables : elle ne garantit rien.

L'encapsulation consiste à cacher les attributs d'une classe et à n'offrir à l'extérieur que des méthodes, qui contrôlent chaque modification. La classe devient alors responsable de la cohérence de ses objets : si un compte a un solde négatif, c'est la classe Compte qui est en faute, et elle seule qu'il faut corriger. Ce chapitre présente aussi les membres static, qui appartiennent à la classe plutôt qu'à ses objets, les constantes, les énumérations, les paquets et les records.

Objectifs du chapitre

  • Choisir la visibilité des membres d'une classe (private, public, visibilité de paquet) et écrire des accesseurs.
  • Énoncer l'invariant d'une classe et le faire respecter par ses constructeurs et ses méthodes.
  • Écrire des classes immuables.
  • Distinguer membres d'instance et membres de classe (static), écrire des constantes et des classes utilitaires.
  • Définir et employer des énumérations, des paquets et des records.

8.1 Visibilité des membres

Définition 8.1Modificateurs d'accès

Chaque attribut, méthode ou constructeur a une visibilité, fixée par un modificateur placé devant sa déclaration :

  • private : accessible seulement dans la classe elle-même ;
  • aucun modificateur (visibilité de paquet) : accessible dans toutes les classes du même paquet (section 8.6) ;
  • protected : comme la visibilité de paquet, plus les sous-classes (chapitre 9) ;
  • public : accessible de partout.

Le compilateur fait respecter ces règles. Si l'attribut solde est privé, le programme qui tente de le modifier directement ne compile pas :


class Compte {
    private double solde;

    double getSolde() {
        return solde;
    }
}

public class ErreurPrivate {
    public static void main(String[] args) {
        Compte c = new Compte();
        c.solde = 1000000;   // interdit : solde est privé
    }
}

ErreurPrivate.java:12: error: solde has private access in Compte
        c.solde = 1000000;   // interdit : solde est privé
         ^
1 error

Méthode : la règle par défaut

  • Les attributs sont private, sauf raison exceptionnelle (une constante, voir plus loin).
  • Les méthodes qui forment le service rendu par la classe sont public ; les méthodes auxiliaires, qui ne servent qu'à l'intérieur, sont private.
  • On ne rend public que ce dont l'extérieur a réellement besoin : tout ce qui est public devra être maintenu tel quel, car d'autres programmes s'en servent.

8.1.1 Accesseurs et modificateurs

Un attribut privé se lit et se modifie par des méthodes publiques. Par convention, la méthode qui lit l'attribut solde s'appelle getSolde() (un accesseur, ou getter), celle qui le modifie setSolde(double s) (un modificateur, ou setter). Pour un attribut booléen, l'accesseur s'appelle souvent isActif() ou estActif().

Écrire mécaniquement un getter et un setter pour chaque attribut ne protège rien : un setter qui accepte n'importe quelle valeur équivaut à un attribut public. L'intérêt des méthodes est de pouvoir refuser une modification incohérente, ou de n'offrir que des opérations qui ont un sens : sur un compte, on ne fixe pas le solde, on dépose et on retire.

8.2 L'invariant d'une classe

Définition 8.2Invariant de classe

L'invariant d'une classe est une propriété que tout objet de la classe vérifie à tout moment où il est accessible de l'extérieur : à la sortie du constructeur, et avant et après chaque appel de méthode publique.

Pour un compte bancaire sans découvert autorisé, l'invariant est « le solde est positif ou nul ». L'encapsulation permet de le garantir : il suffit que le constructeur l'établisse et que chaque méthode publique le préserve, car aucune autre partie du programme ne peut toucher au solde.


// Un compte encapsulé : le solde ne peut changer que par des opérations contrôlées.
class Compte {
    private final String titulaire;
    private double solde;          // invariant : solde >= 0

    Compte(String titulaire, double soldeInitial) {
        if (soldeInitial < 0) {
            throw new IllegalArgumentException("solde initial négatif : " + soldeInitial);
        }
        this.titulaire = titulaire;
        this.solde = soldeInitial;
    }

    public String getTitulaire() {
        return titulaire;
    }

    public double getSolde() {
        return solde;
    }

    public void deposer(double montant) {
        if (montant <= 0) {
            throw new IllegalArgumentException("dépôt non positif : " + montant);
        }
        solde += montant;
    }

    // Retire montant si le solde le permet ; retourne vrai si le retrait a eu lieu.
    public boolean retirer(double montant) {
        if (montant <= 0 || montant > solde) {
            return false;            // l'invariant solde >= 0 est préservé
        }
        solde -= montant;
        return true;
    }
}

public class TestCompteEncapsule {
    public static void main(String[] args) {
        Compte c = new Compte("Alice", 100);
        System.out.println("retrait de 30 : " + c.retirer(30));
        System.out.println("retrait de 500 : " + c.retirer(500));
        System.out.printf("solde de %s : %.2f%n", c.getTitulaire(), c.getSolde());
        c.deposer(-20);    // lève une exception
    }
}

retrait de 30 : true
retrait de 500 : false
solde de Alice : 70.00
Exception in thread "main" java.lang.IllegalArgumentException: dépôt non positif : -20.0
    at Compte.deposer(TestCompteEncapsule.java:24)
    at TestCompteEncapsule.main(TestCompteEncapsule.java:45)

Deux manières de réagir à une demande incohérente apparaissent ici.

8.2.1 Un invariant plus riche : les fractions

Une fraction a une infinité d'écritures : . Si l'on impose l'invariant « le dénominateur est strictement positif et la fraction est irréductible », chaque rationnel a une seule représentation : comparer deux fractions revient à comparer leurs attributs, et l'affichage est toujours le plus simple possible.

La suite du cours est sur Adloun

Le chapitre en entier, formules et figures comprises, avec le tuteur, les fiches, les QCM et les exercices.

Lire la suite du cours

En accès libre, sans inscription.

Exercices corrigés de ce chapitre

Lire la suite du cours →