Aller au contenu

Semaine 2 : JOUR 2 - POO SUITE - ENCAPSULATION, GETTERS / SETTERS, INVARIANTS MÉTIER, EXCEPTION


Objectifs pédagogiques

Cette journée est complémentaire à la journée 6 sur la POO et permet de consolider les précédents acquis avant d’aborder l’héritage, les interfaces, les classes abstraites et le polymorphisme.

À l’issue de cette journée, vous serez capable de :


Mise en contexte (COBOL → Java)

En COBOL, les contrôles métier sont souvent :

Exemple typique COBOL :

IF SOLDE < 0
    MOVE "KO" TO STATUT
END-IF

IF NUMERO-COMPTE = SPACES
    MOVE "ERREUR" TO CODE-RETOUR
END-IF

Rien n’empêche un autre paragraphe de modifier SOLDE sans contrôle.

En Java orienté objet :


Encapsulation : Rappel et approfondissement

L’Encapsulation est une notion trop rapidement abordée en POO, on se contente de générer des méthodes getXXX() et setXXX() (moi y compris) sans forcément avoir conscience des conséquences que cela peut avoir sur l’application même de ce principe de base en POO.

Voici un document qui va vous permettre de bien saisir comment mettre en pratique cette règle de base, même si l’essentiel est résumé dans ce cours.

Définition

L’encapsulation consiste à :

Déclaration d’un attribut private :

private BigDecimal solde;

Analogie COBOL


Getters et setters : à quoi servent-ils vraiment ?

Exemple :

public String getNom() {
    return nom; // ou this.nom
}

public BigDecimal getSolde() {
    return solde; // ou this.solde
}

Erreur fréquente

Exemple de code :

public void setSolde(BigDecimal solde) {
    this.solde = solde;
}

Cette méthode ci-dessus annule toute l’encapsulation et permet d’initialiser le solde avec n’importe quelle valeur !


Quand utiliser un setter ? Quand l’interdire ?

Règle générale (très importante)

Un setter (méthode) ne doit exister que si la modification est légitime et indispensable.

Exemple de règles métier


Invariant métier : notion clé

Un invariant métier est :


Exemples de règles pour un compte bancaire


Invariants avec le constructeur

Mauvaise pratique (objet incomplet)

Compte c = new Compte(); // flou car : Le compte est créé sans solde initial (null ou 0 ?)
c.crediter(new BigDecimal("100")); // que se passe t-il si le solde est null ?

L’objet existe dans un état flou (inconsistent state en anglais) car il est instancié avec un constructeur vide et par conséquent ses attributs ne sont pas initialisés. Les valeurs peuvent devenir incohérentes ! Nous verrons que ce type de constructeur a toutefois un intérêt avec JPA/Hibernate.


Ce qu’il faut faire (éviter l’état flou)

public Compte(String numero, BigDecimal soldeInitial) {
    if (numero == null || numero.isBlank()) {
        throw new IllegalArgumentException("Numéro de compte obligatoire");
    }
    if (soldeInitial.compareTo(BigDecimal.ZERO) < 0) {
        throw new IllegalArgumentException("Solde initial négatif interdit");
    }
    this.numero = numero;
    this.solde = soldeInitial;
}

Invariants dans les méthodes

Exemple : débit sécurisé

public void debiter(BigDecimal montant) {
    if (montant.compareTo(BigDecimal.ZERO) <= 0) {
        throw new IllegalArgumentException("Montant invalide");
    }
    if (solde.compareTo(montant) < 0) {
        throw new IllegalStateException("Solde insuffisant");
    }
    solde = solde.subtract(montant);
}

Classe Client

Pourquoi une nouvelle classe ?

Jusqu’à maintenant, on manipule des comptes isolés et ce n’est pas très réaliste !


Classe Client – première modélisation

Version simple

public class Client {

    private String nom;
    private String prenom;

    public Client(String nom, String prenom) {
        if (nom == null || nom.isBlank()) {
            throw new IllegalArgumentException("Nom obligatoire");
        }
        if (prenom == null || prenom.isBlank()) {
            throw new IllegalArgumentException("Prénom obligatoire");
        }
        this.nom = nom;
        this.prenom = prenom;
    }

    public String getNom() {
        return nom;
    }

    public String getPrenom() {
        return prenom;
    }
}

Relation Client et Compte (première approche)

Approche naïve

public class Compte {
    private Client titulaire;
}
public class Client {
    private Compte compte;
}

Problème : relation rigide, irréaliste car comment associer plusieurs comptes à un Client ?


Approche réaliste (un client, plusieurs comptes)

import java.util.ArrayList;
import java.util.List;

public class Client {

    private String nom;
    private String prenom;
    private List<Compte> comptes = new ArrayList<>(); // notre Collection pour stocker nos comptes

    public void ajouterCompte(Compte compte) {
        comptes.add(compte);
    }
}

Correspondance COBOL : table OCCURS de comptes par client


Démonstration guidée

Client client = new Client("Faye", "Gaël");

Compte compte1 = new Compte("FR001", new BigDecimal("1000.00"));
Compte compte2 = new Compte("FR002", new BigDecimal("500.00"));

client.ajouterCompte(compte1);
client.ajouterCompte(compte2);

compte1.retrait(new BigDecimal("200.00"));

Travaux pratiques

TP 1 – Sécurisation complète de la classe Compte

Consignes :


TP 2 – Implémentation de la classe Client

Consignes :


TP 3 – Scénario métier complet

Consignes :

  1. Créer un client
  2. Créer deux comptes valides
  3. Associer les comptes au client
  4. Effectuer des opérations
  5. Tenter une opération invalide et observer le comportement

Corrigés détaillés

Corrigé TP 1 – Classe Compte robuste

public class Compte {

    private String numero;
    private BigDecimal solde;

    public Compte(String numero, BigDecimal soldeInitial) {
        if (numero == null || numero.isBlank()) {
            throw new IllegalArgumentException("Numéro obligatoire");
        }
        if (soldeInitial.compareTo(BigDecimal.ZERO) < 0) {
            throw new IllegalArgumentException("Solde initial invalide");
        }
        this.numero = numero;
        this.solde = soldeInitial;
    }

    public void crediter(BigDecimal montant) {
        if (montant.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Montant invalide");
        }
        solde = solde.add(montant);
    }

    public void debiter(BigDecimal montant) {
        if (montant.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Montant invalide");
        }
        if (solde.compareTo(montant) < 0) {
            throw new IllegalStateException("Solde insuffisant");
        }
        solde = solde.subtract(montant);
    }

    public BigDecimal getSolde() {
        return solde;
    }
}

Corrigé TP 2 – Classe Client

public class Client {

    private String nom;
    private String prenom;
    private List<Compte> comptes = new ArrayList<>();

    public Client(String nom, String prenom) {
        if (nom == null || nom.isBlank()) {
            throw new IllegalArgumentException("Nom obligatoire");
        }
        if (prenom == null || prenom.isBlank()) {
            throw new IllegalArgumentException("Prénom obligatoire");
        }
        this.nom = nom;
        this.prenom = prenom;
    }

    public void ajouterCompte(Compte compte) {
        comptes.add(compte);
    }

    public List<Compte> getComptes() {
        return comptes;
    }
}

Erreurs fréquentes

  1. Créer des setters pour tous les attributs par réflexe
  2. Mettre la validation hors de l’objet
  3. Créer des objets invalides puis les corriger ensuite
  4. Multiplier les contrôles dans main
  5. Confondre encapsulation et complexité inutile
  6. Laisser les collections modifiables sans contrôle
  7. Ignorer les invariants métier
  8. Utiliser public par facilité
  9. Ne pas comprendre les exceptions
  10. Résister au changement de modèle mental

Avancement du TP fil rouge bancaire (BankLite)

Aujourd’hui, vous savez :

Décisions structurantes :


Synthèse de la journée

Vous avez franchi une nouvelle étape :


Préparation du Jour 8

Le Jour 8 abordera :

Les analogies COBOL seront encore un peu présentes, mais deviendront secondaires.