Aller au contenu principal

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.

javajava
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.

javajava
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 :

javajava
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

  1. Modélisez un intervalle de dates immuable (DateRange). Le constructeur doit empêcher end d’être antérieur à start et proposer une méthode overlaps(DateRange other).
  2. 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.