Modélisation objet
Progression
#Modéliser avec classes, objets et invariants
La programmation orientée objet ne se réduit pas à poser des getters et des setters. Toute classe pertinente raconte une histoire : elle transporte un état cohérent et propose les gestes permettant de le faire évoluer sans casser les règles du métier. Pour donner chair à cette idée, nous repartons d’un exemple courant – la gestion d’un compte bancaire – et nous détaillons chaque décision de conception.
#1. Définir le périmètre et les invariants
Avant même d’écrire la première ligne, notez noir sur blanc les contraintes incontournables : un compte possède un IBAN, un solde initialement positif, un découvert interdit. Ces règles deviennent les garde-fous du constructeur et des méthodes publiques.
1public final class CompteBancaire {2 private final String iban;3 private BigDecimal solde;4 5 public CompteBancaire(String iban, BigDecimal soldeInitial) {6 this.iban = Objects.requireNonNull(iban, "IBAN obligatoire");7 if (soldeInitial.signum() < 0) {8 throw new IllegalArgumentException("Solde initial négatif");9 }10 this.solde = soldeInitial;11 }
Les champs restent privés ; on n’expose pas de setter générique mais des méthodes intentionnelles (crediter
, debiter
). Cela pousse les consommateurs à passer par une API métier plutôt qu’à assigner arbitrairement un champ.
#2. Encapsuler les mutations
Chaque opération traduit une règle : créditez en refusant les montants nuls ou négatifs, débitez en vérifiant le découvert. Lorsqu’un enchaînement concurrent est possible, synchronisez localement l’accès à l’état.
1 public synchronized void crediter(BigDecimal montant) {2 verifierMontantStrictementPositif(montant);3 solde = solde.add(montant);4 }5 6 public synchronized void debiter(BigDecimal montant) {7 verifierMontantStrictementPositif(montant);8 if (solde.compareTo(montant) < 0) {9 throw new IllegalStateException("Découvert refusé");10 }11 solde = solde.subtract(montant);12 }13 14 private static void verifierMontantStrictementPositif(BigDecimal montant) {
Le corps de la classe reste court, mais chaque instruction porte un sens. Les règles métier vivent dans le code ; les exceptions décrivent l’anomalie plutôt que de remonter des messages génériques.
#3. Représenter les valeurs immuables avec des records
Certaines données ne doivent jamais changer : une transaction ou un IBAN sont plus simples à manier lorsqu’elles sont immuables. Les records de Java 17 offrent un support natif :
1public record Virement(String source, String cible, BigDecimal montant) {2 public Virement {3 Objects.requireNonNull(source, "Compte source manquant");4 Objects.requireNonNull(cible, "Compte cible manquant");5 if (montant.signum() <= 0) {6 throw new IllegalArgumentException("Montant invalide");7 }8 }9}
Un record garantit l’immutabilité, fournit automatiquement equals
, hashCode
, toString
et laisse un point d’entrée pour vérifier les invariants.
#4. Atelier
- Modélisez un intervalle de dates immuable (
DateRange
). Le constructeur doit empêcherend
d’être antérieur àstart
et proposer une méthodeoverlaps(DateRange other)
. - Concevez une petite machine à états pour une commande (
CREATED → PAID → SHIPPED
). Chaque transition est portée par une méthode explicite et l’état interne ne peut pas être modifié autrement.
En maîtrisant ces fondations, vous construisez des objets qui protègent leurs invariants et rendent explicites les opérations métier, ce qui facilitera la maintenance comme les tests automatés.