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 :
Compte
Client
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 :
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.
getXXX()
setXXX()
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.
L’encapsulation consiste à :
Cacher les données internes
private
Contrôler la validité
Déclaration d’un attribut private :
private BigDecimal solde;
solde
Exemple :
public String getNom() { return nom; // ou this.nom } public BigDecimal getSolde() { return solde; // ou this.solde }
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 !
Un setter (méthode) ne doit exister que si la modification est légitime et indispensable.
Un invariant métier est :
null
Le solde ne peut pas devenir négatif (selon le type de compte)… sauf si autorisation de découvert !
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.
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; }
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); }
Jusqu’à maintenant, on manipule des comptes isolés et ce n’est pas très réaliste !
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; } }
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 ?
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
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"));
Consignes :
ajouterCompte()
getComptes()
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; } }
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; } }
main
public
Aujourd’hui, vous savez :
Décisions structurantes :
Vous avez franchi une nouvelle étape :
encapsulation
Le Jour 8 abordera :
if
Les analogies COBOL seront encore un peu présentes, mais deviendront secondaires.