Visibilité et accesseursDans le dialogue d’impression, choisissez « Enregistrer au format PDF » comme destination.
Retour

Programmation orientée objet · C2 Encapsulation · Chapitre 1 · 5 h

Visibilité et accesseurs

private, public, protected ; getters et setters ; invariants de classe et validation ; pourquoi un attribut public est une erreur de conception.

Le chapitre 2 a écrit une règle dans une méthode : on ne dépose pas un montant négatif, on n'emprunte pas un livre déjà emprunté. C'était censé être le gain de l'objet — un seul endroit où faire respecter une règle.

Sauf que rien n'oblige à passer par là.

compte.deposer(-500);      /* refusé par la méthode */compte.solde = -500;       /* accepté, et personne n'a rien vu */

La deuxième ligne contourne toute la logique de la classe. Tant qu'elle est possible, l'invariant n'est pas une garantie mais une politesse. Ce chapitre la rend impossible.

Les quatre niveaux de visibilité

Un modificateur devant un attribut ou une méthode dit qui a le droit d'y toucher.

ModificateurVisible depuis
privatela classe elle-même, et rien d'autre
(aucun)le paquetage — visibilité par défaut en Java
protectedle paquetage, plus les classes filles (chapitre 5)
publicpartout

Deux remarques sur ce tableau.

La visibilité par défaut n'est pas public, contrairement à ce que beaucoup supposent : c'est la visibilité de paquetage, plus restrictive. Écrire un attribut sans modificateur n'est donc pas neutre, c'est un choix — généralement fait par inadvertance.

protected est plus permissif que la visibilité par défaut, et pas seulement « pour les filles » : il ouvre aussi au paquetage entier. C'est une nuance qui surprend, et une raison de ne pas l'employer par réflexe.

La règle de conduite tient en une phrase : partir de private, et n'ouvrir que ce qu'on peut justifier. Restreindre après coup casse le code des autres ; ouvrir après coup ne casse rien.

Accesseurs

public class Compte {    private double solde;     public double getSolde() { return this.solde; }     public void deposer(double montant) {        if (montant <= 0) throw new IllegalArgumentException("montant non positif");        this.solde += montant;    }}

Trois choses à observer, et la troisième est le vrai sujet du chapitre.

Un accesseur en lecturegetSolde — laisse consulter sans laisser modifier. C'est le cas le plus fréquent, et le plus inoffensif.

Il n'y a pas de setSolde, et c'est délibéré. Un solde ne se fixe pas, il se modifie par des opérations métier : deposer, retirer. Fournir un setSolde public rétablirait exactement le trou qu'on vient de boucher.

Un accesseur n'est pas obligatoire. L'erreur la plus répandue chez les débutants est de générer mécaniquement un getter et un setter pour chaque attribut — l'éditeur le propose en un clic. Le résultat est une classe dont tous les champs sont modifiables de l'extérieur, avec deux lignes de cérémonie en plus : l'encapsulation est perdue, et le code est plus long.

La question à se poser pour chaque attribut est donc : qui a besoin de le lire ? qui a besoin de le modifier, et sous quelles conditions ? Le plus souvent, la réponse à la seconde question est « personne, sinon la classe elle-même ».

L'invariant de classe

C'est la notion que le chapitre doit installer, et elle donne un critère objectif.

Un invariant de classe est une propriété qui doit être vraie de la fin du constructeur jusqu'à la fin de la vie de l'objet, et qu'aucune méthode publique ne doit pouvoir violer.

Compte :   solde ≥ 0   et   titulaire ≠ nullLivre  :   si emprunteur ≠ null alors disponible = falseDate   :   1 ≤ jour ≤ nombre de jours du mois

Deux devoirs en découlent, et ils se répartissent clairement.

Le constructeur l'établit. Un objet ne doit pas pouvoir naître dans un état invalide : c'est le rôle de la validation vue au chapitre 2. Un constructeur qui accepte n'importe quoi reporte le problème sur toutes les méthodes.

Chaque méthode publique le préserve. Elle peut le rompre temporairement à l'intérieur — le temps d'une suite d'affectations — mais il doit être rétabli quand elle rend la main.

L'invariant donne enfin la réponse à « faut-il un setter ? ». Un setter est acceptable s'il peut vérifier l'invariant :

public void setTitulaire(String titulaire) {    if (titulaire == null || titulaire.isBlank()) {        throw new IllegalArgumentException("titulaire vide");    }    this.titulaire = titulaire;}

Un setter qui se contente de this.x = x n'est pas un setter : c'est un attribut public écrit en trois lignes.

Quiz · 1 question

Un étudiant déclare tous ses attributs private, puis génère un getter et un setter pour chacun. Qu'a-t-il gagné ?

  • L'encapsulation complète : les attributs ne sont plus accessibles directementencapsulation complète
  • Presque rien : n'importe qui peut toujours mettre n'importe quelle valeur, à travers le setter. L'encapsulation n'est pas de cacher les attributs mais de contrôler qui peut les changer, et sous quelles conditionspresque rien
  • Rien du tout, et c'est même une régression : le code est plus long et plus lentrégression

Réponse : Un setter qui se contente de this.x = x expose exactement ce qu'un attribut public exposait : n'importe quel appelant peut y mettre n'importe quoi, l'invariant n'est pas protégé, et le code a seulement gagné deux lignes de cérémonie. Le gain réel n'est pas nul pour autant — la classe peut plus tard ajouter une validation, journaliser, ou changer sa représentation interne sans casser ses appelants, ce qu'un attribut public interdirait. Mais tant que le setter est vide de règles, cette possibilité n'est pas exploitée. La bonne démarche est de se demander, attribut par attribut : qui a besoin de le lire ? qui a besoin de le modifier, et sous quelles conditions ? Le plus souvent, la réponse à la seconde question est « personne, sinon la classe » — et l'on écrit alors des opérations métier (deposer, retirer) plutôt qu'un setter.

Pourquoi un attribut public est une erreur de conception

Quatre raisons, indépendantes les unes des autres.

On ne peut plus rien vérifier. C'est le cas de l'introduction, et le plus visible.

On ne peut plus changer la représentation interne. Si Point expose x et y publics, tout le code utilisateur écrit p.x — et passer plus tard en coordonnées polaires devient impossible sans réécrire chaque appelant. Avec getX(), la conversion se fait dans la méthode et personne d'autre n'est touché. C'est le bénéfice le moins visible et le plus durable.

On ne peut rien ajouter au passage. Journaliser les modifications, mettre un cache à jour, notifier un observateur, verrouiller pour l'accès concurrent : tout cela suppose un point de passage. Un attribut public n'en a pas.

On perd la lecture seule. Un attribut public est lisible et modifiable, sans nuance. Un getter sans setter donne exactement ce que final ne peut pas toujours exprimer.

Une fuite plus subtile

Encapsuler les attributs ne suffit pas toujours, et c'est le piège avancé du chapitre.

public class Reservation {    private Date debut;    public Date getDebut() { return this.debut; }     /* fuite */}

L'attribut est private, le getter ne fait que lire — et pourtant l'invariant n'est pas protégé. getDebut() rend la référence vers l'objet Date interne, au sens du chapitre 2 : l'appelant peut donc appeler setTime dessus et modifier la réservation de l'extérieur, sans jamais passer par une méthode de Reservation.

Deux parades. Rendre une copiereturn new Date(this.debut.getTime()) — ou, bien mieux, employer un type immuable, comme LocalDate en Java moderne : un objet qu'on ne peut pas modifier ne peut pas fuir.

La règle générale : l'encapsulation porte sur ce que l'objet laisse faire, pas seulement sur ce qu'il laisse voir. Rendre une référence vers une structure interne modifiable — un tableau, une liste, une date mutable — revient à la rendre publique.

Et l'excès inverse

Une nuance, pour ne pas transformer la règle en rituel.

Une classe qui n'est **qu'**un ensemble de getters et de setters, sans aucun comportement, est un symptôme : la logique qui devrait lui appartenir a été écrite ailleurs, chez ses appelants. On parle alors de modèle anémique, et l'on retombe sur le procédural du chapitre 1 avec plus de cérémonie.

Le remède tient en une formule : « dire, ne pas demander ». Plutôt que

if (compte.getSolde() >= montant) { compte.setSolde(compte.getSolde() - montant); }

on écrit compte.retirer(montant), et la règle reste où elle doit être. Le premier code met la logique chez l'appelant — donc dupliquée partout où l'on retire — le second la met dans l'objet qui possède la donnée.

Quiz · 1 question

Une classe Reservation a un attribut private Date debut et un getter qui rend cet objet. L'invariant est-il protégé ?

  • Oui : l'attribut est private, et le getter ne fait que lire sans permettre l'affectationprotégé
  • Non : le getter rend la RÉFÉRENCE vers l'objet Date interne, que l'appelant peut ensuite modifier avec setTime — sans jamais passer par une méthode de Reservationfuite de référence
  • Non, mais uniquement si la classe est dans un autre paquetagequestion de paquetage

Réponse : C'est le piège avancé de l'encapsulation, et il découle du chapitre 2 : une variable ne contient pas l'objet mais une RÉFÉRENCE vers lui. Rendre cette référence, c'est donner à l'appelant exactement la même prise que celle de la classe. Il ne peut certes pas faire reservation.debut = ..., mais il peut faire reservation.getDebut().setTime(...), ce qui modifie l'état interne sans passer par aucune méthode. Deux parades : rendre une COPIE défensive, ou — bien mieux — employer un type IMMUABLE comme LocalDate, qu'on ne peut pas modifier et qui ne peut donc pas fuir. La règle générale : l'encapsulation porte sur ce que l'objet laisse FAIRE, pas seulement sur ce qu'il laisse voir. Rendre un tableau ou une liste interne pose exactement le même problème.

À vous

L'exercice suit le fil de bibliothèque du semestre, et procède par dégradations successives.

D'abord un Livre à attributs publics : vous cassez son invariant en deux lignes, sans que rien ne proteste. Puis vous encapsulez, et vous constatez que les mêmes deux lignes ne compilent plus — ici, ne s'exécutent plus.

Ensuite le piège de la référence qui fuit : la classe rend sa liste d'emprunteurs, et l'appelant la vide. Vous écrirez les deux parades.

Enfin la démonstration du « dire, ne pas demander » : la même opération écrite chez l'appelant puis dans l'objet, et ce qui se passe quand la règle change.

Exercice de code

Cassez un invariant par des attributs publics, encapsulez, puis réparez la fuite de référence.

Point de départ

// ── 1. Attributs publics : l'invariant ne tient à rien ────────────────────
class LivreOuvert {
  constructor(titre) {
    this.titre = titre;
    this.disponible = true;
    this.emprunteur = null;
  }
  // Invariant voulu : emprunteur non nul  <=>  disponible faux.
  emprunter(qui) {
    if (!this.disponible) return false;
    this.disponible = false;
    this.emprunteur = qui;
    return true;
  }
  invariantOk() {
    return (this.emprunteur === null) === this.disponible;
  }
  toString() {
    return this.titre + " [" + (this.disponible ? "libre" : "pris par " + this.emprunteur) + "]";
  }
}

// ── 2. Version encapsulée ─────────────────────────────────────────────────
class Livre {
  #titre; #disponible; #emprunteurs;      // # = champ réellement privé

  constructor(titre) {
    if (!titre) throw new Error("un livre doit avoir un titre");
    this.#titre = titre;
    this.#disponible = true;
    this.#emprunteurs = [];
  }
  get titre() { return this.#titre; }
  estDisponible() { return this.#disponible; }

  emprunter(qui) {
    // ← à écrire : refuser si déjà emprunté, sinon enregistrer l'emprunteur
    return false;
  }
  rendre() { this.#disponible = true; }

  // ← FUITE : ce getter rend la liste INTERNE, que l'appelant peut vider.
  getEmprunteurs() { return this.#emprunteurs; }
}

// ── À VOUS ────────────────────────────────────────────────────────────────
// 1. Cassez l'invariant de LivreOuvert en DEUX lignes, sans appeler emprunter.
// 2. Écrivez emprunter() dans Livre, et vérifiez que la même attaque échoue.
// 3. Réparez la fuite de getEmprunteurs, de deux façons : copie défensive,
//    puis exposition d'une valeur immuable.
// 4. Écrivez la même opération « emprunter si disponible » chez l'appelant,
//    puis dans l'objet. Que se passe-t-il quand la règle change ?

const a = new LivreOuvert("Dune");
console.log("   " + a.toString() + "   invariant :", a.invariantOk());

Solution

class LivreOuvert {
  constructor(titre) { this.titre = titre; this.disponible = true; this.emprunteur = null; }
  emprunter(qui) {
    if (!this.disponible) return false;
    this.disponible = false; this.emprunteur = qui; return true;
  }
  invariantOk() { return (this.emprunteur === null) === this.disponible; }
  toString() {
    return this.titre + " [" + (this.disponible ? "libre" : "pris par " + this.emprunteur) + "]";
  }
}

console.log("— 1. attributs publics : deux lignes suffisent —");
const a = new LivreOuvert("Dune");
console.log("   départ    : " + a.toString() + "   invariant : " + a.invariantOk());
a.emprunteur = "Ana";        // on contourne entièrement emprunter()
a.disponible = true;         // et l'on ment sur la disponibilité
console.log("   après     : " + a.toString() + "   invariant : " + a.invariantOk());
console.log("   Le livre est « libre » et pris par Ana. Aucune erreur, aucune trace.");

class Livre {
  #titre; #disponible; #emprunteurs;

  constructor(titre) {
    if (!titre) throw new Error("un livre doit avoir un titre");
    this.#titre = titre;
    this.#disponible = true;
    this.#emprunteurs = [];
  }
  get titre() { return this.#titre; }
  estDisponible() { return this.#disponible; }

  emprunter(qui) {
    // La règle, en un seul endroit — et cette fois on ne peut plus l'éviter.
    if (!this.#disponible) return false;
    if (!qui) throw new Error("emprunteur non renseigné");
    this.#disponible = false;
    this.#emprunteurs.push(qui);
    return true;
  }
  rendre() { this.#disponible = true; }

  // Copie défensive : l'appelant reçoit SA liste, pas la nôtre.
  getEmprunteurs() { return [...this.#emprunteurs]; }
  // Mieux encore : n'exposer qu'une valeur immuable, ici un simple compte.
  get nombreEmprunts() { return this.#emprunteurs.length; }

  toString() {
    return this.#titre + " [" + (this.#disponible ? "libre" : "emprunté") +
           ", " + this.#emprunteurs.length + " emprunt(s)]";
  }
}

console.log("");
console.log("— 2. la même attaque, sur la version encapsulée —");
const b = new Livre("Dune");
b.emprunter("Ana");
b.disponible = true;         // crée un champ PUBLIC sans rapport, inoffensif
console.log("   " + b.toString());
console.log("   estDisponible() rend toujours " + b.estDisponible() +
            " : le champ privé est intact, l'invariant tient.");
try { console.log(b.#titre); } catch (e) { console.log("   accès direct à #titre : refusé"); }

console.log("");
console.log("— 3. la fuite de référence —");
const c = new Livre("Solaris");
c.emprunter("Bo"); c.rendre(); c.emprunter("Cy");
const liste = c.getEmprunteurs();
liste.length = 0;            // l'appelant vide SA copie
console.log("   après que l'appelant a vidé la liste reçue : " + c.toString());
console.log("   Sans la copie défensive, le compte serait tombé à 0 : l'appelant");
console.log("   aurait modifié l'état interne sans passer par aucune méthode.");
console.log("   nombreEmprunts (valeur immuable) : " + c.nombreEmprunts + " — rien à fuir.");

console.log("");
console.log("— 4. dire, ne pas demander —");
// Chez l'appelant : la règle est ici, donc dupliquée partout où l'on emprunte.
function emprunterChezAppelant(livre, qui) {
  if (livre.estDisponible()) { livre.emprunter(qui); return true; }
  return false;
}
// Dans l'objet : la règle est là où vit la donnée.
const d = new Livre("Ubik");
console.log("   premier emprunt : " + d.emprunter("Ana"));
console.log("   second emprunt  : " + d.emprunter("Bo") + "   (refusé par l'objet)");
console.log("   Le jour où la règle devient « deux emprunts simultanés autorisés »,");
console.log("   la version « chez l'appelant » demande de retrouver tous les appels ;");
console.log("   la version « dans l'objet » demande de modifier une méthode.");

En travaux pratiques

Travaux pratiques 3 · 3 h

L'encapsulation qui fuit

Vérifier qu'un champ privé peut être modifié de l'extérieur si l'accesseur est mal écrit — et découvrir que l'invariant se protège, pas se déclare.

Avant de commencer

  • Le TP 2
  • Les collections de base de Java

Énoncé

  1. Casser l'invariantRendez publics les champs de Document. Écrivez du code extérieur qui met une année à moins mille et une disponibilité incohérente. Comptez les lignes qu'il vous a fallu.
  2. FermerRepassez tout en privé, ajoutez des accesseurs, et vérifiez que le code précédent ne compile plus.
  3. La fuite par l'accesseurAjoutez à Mediatheque une liste privée de documents et un accesseur qui la renvoie. Depuis l'extérieur, videz la liste. Constatez que le privé n'a rien protégé. Indice : Renvoyer une référence sur une collection mutable, c'est en donner les clés.
  4. Colmater, trois façonsCorrigez en renvoyant une copie, puis une vue non modifiable, puis en ne renvoyant rien du tout et en exposant les opérations utiles. Comparez les trois.
  5. La fuite par le constructeurPassez une liste au constructeur de Mediatheque et gardez-en la référence. Modifiez ensuite la liste d'origine depuis l'extérieur. Corrigez.
  6. Les accesseurs inutilesComptez, dans votre code, les accesseurs qui ne servent qu'à recopier un champ vers l'extérieur. Pour chacun, demandez-vous quelle question du domaine il répond.
  7. Déplacer le comportementTrouvez une portion de code extérieur qui lit trois accesseurs pour décider quelque chose. Déplacez cette décision DANS la classe et supprimez les accesseurs devenus inutiles.
  8. Le champ finalRendez immuables les champs qui ne changent jamais après construction. Vérifiez ce que le compilateur refuse désormais.

C'est réussi quand

  • Vous videz une liste « privée » depuis l'extérieur, en une ligne
  • Après correction, la même ligne ne compile plus ou n'a plus d'effet
  • Vous avez supprimé au moins deux accesseurs en déplaçant une décision

Correction

La fuite par l'accesseur
public class Mediatheque {
  private List<Document> documents = new ArrayList<>();
  public List<Document> getDocuments() { return documents; }   /* FUITE */
}

/* depuis l'extérieur */
mediatheque.getDocuments().clear();       /* tout est effacé */
mediatheque.getDocuments().add(null);     /* un null dans la collection */

Le champ est privé, la référence ne l'est pas. Renvoyer une collection mutable revient à la rendre publique — et c'est la fuite d'encapsulation la plus fréquente en Java, précisément parce que le mot private donne l'impression que le travail est fait.

Les trois corrections
/* 1. copie défensive : l'appelant modifie SA copie */
public List<Document> getDocuments() { return new ArrayList<>(documents); }

/* 2. vue non modifiable : moins coûteux, échoue à l'exécution */
public List<Document> getDocuments() {
  return Collections.unmodifiableList(documents);
}

/* 3. ne rien exposer : exposer les OPÉRATIONS */
public int nombreDeDocuments()          { return documents.size(); }
public Optional<Document> chercher(String titre) { … }
public void ajouter(Document d)         { … validation … }

La troisième est la meilleure, et c'est celle qu'on écrit le moins. Les deux premières laissent l'appelant décider quoi faire de la liste ; la troisième garde la décision dans la classe qui possède l'invariant. La deuxième a un défaut à connaître : elle échoue à l'exécution, pas à la compilation, et la vue reflète les modifications internes ultérieures.

La fuite par le constructeur
public Mediatheque(List<Document> initiaux) {
  this.documents = initiaux;          /* FUITE : référence partagée */
}

List<Document> liste = new ArrayList<>();
Mediatheque m = new Mediatheque(liste);
liste.clear();                          /* la médiathèque est vidée */

/* correction : copier À L'ENTRÉE aussi */
this.documents = new ArrayList<>(initiaux);

L'encapsulation se protège aux DEUX bouts : ce qui entre et ce qui sort. On l'oublie systématiquement à l'entrée, parce que le danger est moins visible. La règle : toute collection ou tout objet mutable reçu de l'extérieur, et conservé, se copie.

L'accesseur qui ne répond à aucune question
/* AVANT : la décision est dehors */
if (doc.getAnnee() < 1900
  && doc.getEtat().equals("fragile")
  && !doc.estDisponible()) { … }

/* APRÈS : la décision est dans l'objet */
if (doc.necessiteUneConsultationSurPlace()) { … }

/* et getEtat(), getAnnee() peuvent disparaître de l'interface publique */

Un accesseur n'est pas une faute en soi ; c'en est une quand il sert à prendre au-dehors une décision qui appartient à l'objet. Le symptôme est facile à repérer : du code extérieur qui lit plusieurs accesseurs du même objet pour conclure quelque chose. Ce qu'on appelle « tell, don't ask » — demandez à l'objet d'agir, ne lui soutirez pas ses données pour agir à sa place.

final, et ce qu'il garantit
private final String titre;      /* fixé à la construction, définitif */
private boolean disponible;      /* varie légitimement */

titre = "autre";                 /* ERREUR DE COMPILATION */

/* mais attention : */
private final List<Document> docs = new ArrayList<>();
docs.add(...);                   /* AUTORISÉ : la RÉFÉRENCE est finale,
                                  pas le contenu */

final s'applique à la référence, jamais à l'objet pointé. C'est une garantie vérifiée à la COMPILATION, donc gratuite, et elle documente l'intention mieux qu'un commentaire. La distinction entre référence immuable et objet immuable est exactement celle du pointeur constant en C — et c'est elle qui rend les copies défensives nécessaires malgré final.

Ce que la suite en fait

Le chapitre 4 complète la boîte à outils de la classe : ce qui appartient à la classe plutôt qu'aux objets — les membres statiques —, et la possibilité de donner plusieurs formes à une même opération, avec la surcharge.

Le chapitre 5 fera réapparaître protected, et l'on comprendra alors pourquoi ce modificateur est plus délicat qu'il n'en a l'air : ouvrir un attribut à ses classes filles, c'est signer un contrat avec du code qui n'est pas encore écrit.

À retenir

Flashcards · 6 cartes

Quels sont les quatre niveaux de visibilité en Java, et lequel surprend ?
private (la classe seule), AUCUN modificateur (le paquetage — c'est la visibilité PAR DÉFAUT, pas public), protected (le paquetage PLUS les classes filles), public (partout). Deux surprises : la visibilité par défaut n'est pas public ; et protected est plus PERMISSIF que le défaut, puisqu'il ouvre au paquetage entier en plus des filles. Règle : partir de private et n'ouvrir que ce qu'on peut justifier — restreindre après coup casse le code des autres, ouvrir ne casse rien.
Qu'est-ce qu'un invariant de classe, et qui en est responsable ?
Une propriété qui doit être vraie DE LA FIN DU CONSTRUCTEUR jusqu'à la fin de la vie de l'objet, et qu'aucune méthode publique ne peut violer — par exemple solde ≥ 0 et titulaire non nul. Le CONSTRUCTEUR l'établit : un objet ne doit pas pouvoir naître invalide. Chaque MÉTHODE PUBLIQUE le préserve : elle peut le rompre temporairement à l'intérieur, mais doit l'avoir rétabli quand elle rend la main.
Un getter et un setter générés pour chaque attribut : qu'a-t-on gagné ?
Presque rien tant que le setter est vide de règles : n'importe qui peut toujours mettre n'importe quoi, avec deux lignes de cérémonie en plus. Le gain potentiel subsiste — pouvoir ajouter plus tard une validation, journaliser, ou changer la représentation interne sans casser les appelants — mais il n'est pas exploité. Un setter n'est légitime que s'il VÉRIFIE l'invariant ; sinon c'est un attribut public écrit en trois lignes. Et souvent il ne faut pas de setter du tout, mais des opérations métier : deposer, retirer.
Pourquoi un attribut public est-il une erreur de conception ? Donnez plus d'une raison.
1) On ne peut plus rien VÉRIFIER. 2) On ne peut plus changer la REPRÉSENTATION INTERNE : si Point expose x et y, tout le code écrit p.x et passer en polaires devient impossible — c'est le bénéfice le moins visible et le plus durable. 3) On ne peut rien AJOUTER au passage : journalisation, cache, notification, verrou supposent un point de passage. 4) On perd la LECTURE SEULE : public est lisible et modifiable, sans nuance.
Pourquoi un getter qui rend un objet mutable est-il une fuite, et que faire ?
Parce qu'il rend la RÉFÉRENCE vers l'objet interne : l'appelant peut le modifier — getDebut().setTime(...) — sans passer par aucune méthode de la classe. Deux parades : rendre une COPIE défensive, ou employer un type IMMUABLE (LocalDate), qu'on ne peut pas modifier et qui ne peut donc pas fuir. Vaut aussi pour un tableau ou une liste interne. Règle : l'encapsulation porte sur ce que l'objet laisse FAIRE, pas seulement sur ce qu'il laisse voir.
Qu'est-ce qu'un modèle anémique, et quelle formule y répond ?
Une classe qui n'est QU'un ensemble de getters et de setters, sans comportement : la logique qui devrait lui appartenir a été écrite chez ses appelants, et l'on retombe sur le procédural avec plus de cérémonie. La formule : « DIRE, NE PAS DEMANDER ». Écrire compte.retirer(montant) plutôt que tester getSolde() puis appeler setSolde() chez l'appelant — sinon la règle est dupliquée partout où l'on retire.