Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 17

Encapsulation : Modificateurs d'accès et Getters/Setters

Recommended resources

Bonjour ! Dans la leçon précédente, nous avons appris à initialiser nos objets de manière fiable à l'aide de constructeurs. Cependant, un problème subsiste : les attributs de nos objets sont toujours directement accessibles et modifiables de l'extérieur, ce qui peut compromettre leur intégrité.

Aujourd'hui, nous allons aborder un pilier de la programmation orientée objet : l'encapsulation. Ce principe consiste à protéger les données (attributs) d'un objet en les rendant inaccessibles directement et en ne fournissant qu'une interface publique (méthodes) pour interagir avec elles. Vous apprendrez à utiliser les modificateurs d'accès private et public pour cacher l'état interne d'un objet et à implémenter des accesseurs (getters) et mutateurs (setters) pour en contrôler la lecture et la modification.

1. Le problème de l'accès direct

Jusqu'à présent, nous avons déclaré nos attributs sans modificateur d'accès explicite, ce qui leur donne une visibilité par défaut. En pratique, cela signifie souvent qu'ils sont public ou accessibles au sein du même package. Imaginons une classe CompteBancaire :

public class CompteBancaire {
    public String titulaire;
    public double solde;
    public String numeroCompte;

    public CompteBancaire(String titulaire, double solde, String numeroCompte) {
        this.titulaire = titulaire;
        this.solde = solde;
        this.numeroCompte = numeroCompte;
    }
}

// Dans une autre classe...
CompteBancaire monCompte = new CompteBancaire("Alice", 1000.0, "FR123456");

// Problèmes potentiels :
monCompte.solde = -5000.0; // État incohérent ! Un solde ne devrait pas pouvoir être négatif arbitrairement.
monCompte.numeroCompte = "unAutreNumero"; // On modifie un identifiant qui devrait être immuable.

Dans cet exemple, n'importe quelle partie du code peut altérer l'état interne de l'objet monCompte, sans aucune validation. L'objet perd tout contrôle sur sa propre cohérence. C'est comme si le moteur d'une voiture avait tous ses composants exposés, permettant à quiconque de les manipuler directement, au risque de tout casser.


2. L'encapsulation : protéger l'état de l'objet

L'encapsulation vise à résoudre ce problème en créant une "capsule" protectrice autour des données de l'objet. L'état interne (les attributs) est caché, et l'objet expose un ensemble de "boutons" (méthodes publiques) qui constituent la seule manière d'interagir avec lui.

L'encapsulation fonctionne comme une "boîte noire". L'extérieur (le programme) ne voit et n'utilise que l'interface publique (les méthodes), sans avoir à connaître les détails de l'implémentation interne (les attributs privés).

La vidéo suivante explique très bien pourquoi il est fondamental de protéger les attributs d'une classe pour garantir la cohérence des données.

Java - public, protected, private : Visibilité d'une classe et de ses attributs

Regardez cette courte section de la vidéo "Java - public, protected, private" de la chaîne Petits Bouts De Code. Elle met en lumière les risques liés à l'accès direct aux attributs.

Concentrez-vous sur le passage qui explique l'importance de protéger les attributs. Notez les deux problèmes majeurs soulevés : le non-respect des contraintes sur les valeurs et l'incohérence entre les attributs.


3. Les modificateurs d'accès : private et public

Pour mettre en œuvre l'encapsulation, Java fournit des modificateurs d'accès. Les deux plus importants pour nous aujourd'hui sont :

  • public : Le membre (attribut ou méthode) est accessible depuis n'importe où dans le code.
  • private : Le membre est accessible uniquement depuis l'intérieur de la classe où il est déclaré.

La bonne pratique fondamentale de l'encapsulation est simple :

  1. Déclarer tous les attributs private.
  2. Déclarer public les méthodes qui doivent former l'interface de l'objet.

Reprenons notre classe CompteBancaire :

public class CompteBancaire {
    private String titulaire;
    private double solde;
    private String numeroCompte;
    
    // ... constructeur
}

// Dans une autre classe...
CompteBancaire monCompte = new CompteBancaire("Alice", 1000.0, "FR123456");

// monCompte.solde = -5000.0; // ERREUR DE COMPILATION ! L'attribut 'solde' est privé.

Le compilateur nous empêche désormais de modifier directement l'état de l'objet. Nous avons bien protégé nos données, mais comment peut-on interagir avec elles légitimement ?


4. Accès contrôlé avec les Getters et Setters

Maintenant que nos données sont privées, nous devons fournir des méthodes publiques pour y accéder de manière contrôlée.

Accesseurs (Getters)

Un getter est une méthode publique qui retourne la valeur d'un attribut privé. Il permet un accès en lecture.

  • Convention de nommage : getNomAttribut()
  • Pour les attributs booléens, la convention est isNomAttribut() (ex: public boolean isActif())
public class CompteBancaire {
    private double solde;
    // ...

    public double getSolde() {
        return this.solde;
    }
}

Mutateurs (Setters)

Un setter est une méthode publique qui modifie la valeur d'un attribut privé. Il permet un accès en écriture contrôlée, car on peut y intégrer de la logique de validation.

  • Convention de nommage : setNomAttribut(Type nouveauParametre)
public class Personne {
    private int age;
    // ...

    public void setAge(int age) {
        if (age > 0 && age < 130) { // Logique de validation
            this.age = age;
        } else {
            System.out.println("Erreur : l'âge est invalide.");
        }
    }
}
Cet autre schéma illustre bien l'idée avec un objet `Voiture`. L'extérieur ne touche pas directement au `prix`, mais passe par les méthodes `getPrix()` et `setPrix()`.

La lecture suivante vous guidera à travers un exemple très concret, en comparant une classe non protégée à une classe correctement encapsulée.

Les bases de la POO en Java : Classes, Objets, Encapsulation et this

Cet extrait de l'article de Rachid Saad est excellent pour comprendre l'avant/après de l'encapsulation. Il montre les problèmes concrets d'une classe mal conçue et comment les résoudre.

Lisez la section "4. L'Encapsulation : protéger l'état de l'objet". Portez une attention particulière aux deux exemples de code : CompteNonProtege (l'approche problématique) et CompteProtege (la solution avec encapsulation). Analysez comment les attributs private et les méthodes public (getters/setters) résolvent les problèmes de modification directe et de validation.

La vidéo ci-dessous résume parfaitement l'implémentation des getters et setters, et les avantages qu'ils procurent pour garantir la cohérence de l'objet.

Java - public, protected, private : Visibilité d'une classe et de ses attributs

Regardez ces deux sections de la vidéo "Java - public, protected, private". Elles montrent concrètement comment implémenter et utiliser les getters et setters.

D'abord, regardez la partie expliquant les getters et setters. Elle définit leurs rôles et conventions. Ensuite, visionnez l'exemple final qui met en pratique l'encapsulation. Observez comment le code externe utilise getLongueur() et setLongueur() au lieu d'accéder directement à l'attribut, assurant ainsi la validation et la cohérence des données.


5. Exercice pratique : Encapsuler la classe Client

Il est temps de mettre en pratique ce que vous avez appris. Reprenons la classe Client de l'exercice précédent et appliquons les principes de l'encapsulation.

Énoncé :

  1. Reprenez votre classe Client qui possède les attributs id, nom, prenom, et email.
  2. Rendez tous les attributs private.
  3. Générez des getters pour tous les attributs (getId(), getNom(), etc.).
  4. Générez des setters pour nom, prenom et email. Ne créez pas de setter pour l'attribut id, car il doit être immuable une fois le client créé.
  5. Ajoutez de la validation dans vos setters :
    • setNom(String nom) et setPrenom(String prenom) : Vérifiez que la chaîne de caractères n'est ni null ni vide. Si c'est le cas, vous pouvez afficher un message d'erreur ou, pour faire encore mieux, lever une exception (throw new IllegalArgumentException("Le nom ne peut pas être vide");).
    • setEmail(String email) : Vérifiez que l'email contient bien un caractère @.
  6. Modifiez votre classe Main pour utiliser les getters afin d'afficher les informations du client, et testez vos setters avec des valeurs valides et invalides.
Cliquez ici pour voir une solution possible
public class Client {
    private long id;
    private String nom;
    private String prenom;
    private String email;

    // Constructeurs (inchangés, mais ils interagissent désormais implicitement
    // avec des attributs privés)
    public Client(long id, String nom, String prenom, String email) {
        this.id = id;
        this.setNom(nom); // On peut utiliser les setters dans le constructeur pour la validation
        this.setPrenom(prenom);
        this.setEmail(email);
    }

    public Client(String nom, String prenom, String email) {
        this(System.currentTimeMillis(), nom, prenom, email);
    }

    // --- GETTERS ---
    public long getId() {
        return id;
    }

    public String getNom() {
        return nom;
    }

    public String getPrenom() {
        return prenom;
    }

    public String getEmail() {
        return email;
    }
    
    // --- SETTERS ---
    public void setNom(String nom) {
        if (nom == null || nom.trim().isEmpty()) {
            throw new IllegalArgumentException("Le nom ne peut être null ou vide.");
        }
        this.nom = nom;
    }

    public void setPrenom(String prenom) {
        if (prenom == null || prenom.trim().isEmpty()) {
            throw new IllegalArgumentException("Le prénom ne peut être null ou vide.");
        }
        this.prenom = prenom;
    }

    public void setEmail(String email) {
        if (email == null || !email.contains("@")) {
            throw new IllegalArgumentException("L'email est invalide.");
        }
        this.email = email;
    }
    
    @Override
    public String toString() {
        return "Client [id=" + id + ", nom=" + nom + ", prenom=" + prenom + ", email=" + email + "]";
    }
}

// Classe de test
public class Main {
    public static void main(String[] args) {
        Client client1 = new Client("Dupont", "Jean", "jean.dupont@email.com");
        System.out.println("Client créé : " + client1);

        // Utilisation des getters
        System.out.println("Nom du client : " + client1.getNom());

        // Utilisation des setters
        client1.setEmail("nouveau.email@provider.com");
        System.out.println("Email mis à jour : " + client1.getEmail());

        // Test de validation
        try {
            client1.setNom(""); // Tentative de mettre un nom vide
        } catch (IllegalArgumentException e) {
            System.out.println("Erreur interceptée : " + e.getMessage());
        }
        
        System.out.println("Client après tentatives de modification : " + client1);
    }
}

Conclusion

Félicitations ! Vous maîtrisez désormais l'un des principes les plus importants de la programmation orientée objet. L'encapsulation n'est pas juste une contrainte, c'est une pratique qui rend votre code plus sûr, plus maintenable et plus facile à faire évoluer.

Points clés à retenir :

  • L'encapsulation protège l'état interne d'un objet contre les modifications non contrôlées.
  • Les attributs doivent presque toujours être déclarés private.
  • L'accès aux attributs se fait via des méthodes public : les getters pour la lecture et les setters pour l'écriture.
  • Les setters sont le lieu idéal pour implémenter la logique de validation des données, garantissant ainsi que l'objet reste toujours dans un état cohérent.
  • Certains attributs peuvent être en lecture seule (uniquement un getter, pas de setter), comme un identifiant.

Dans la prochaine leçon, nous allons explorer en détail le mot-clé this et introduire une nouvelle notion, les membres statiques. Nous verrons comment créer des attributs et des méthodes qui appartiennent à la classe elle-même plutôt qu'à une instance particulière.