Aller au contenu principal

Constructeur progressif et constructeur par étapes

Les design patterns35 min

Construire lisiblement, puis rendre certains enchaînements impossibles.

Dans cette leçon

Le constructeur qui ne raconte rien

Le constructeur compile, mais il oblige à mémoriser l'ordre des arguments. Les booléens ne racontent rien et une combinaison absurde reste possible.

Sans rouvrir la signature, que signifient true et false ? Et la mozzarella est-elle cohérente avec le booléen végane ? Les types seuls ne suffisent pas à raconter l'intention.

Main.java

Les indications de l'IDE ne règlent pas tout

Un IDE peut afficher le nom des paramètres. Il n'évite ni la multiplication des surcharges, ni les combinaisons invalides, ni la répétition d'une même configuration.

Nommer chaque choix

  1. Démarrer

    builder() crée un objet temporaire avec ses valeurs par défaut.
  2. Ajuster

    Chaque méthode nomme un choix, modifie le constructeur progressif et retourne this pour permettre le chaînage.
  3. Terminer

    build() valide la configuration complète et crée l'objet final.
Pizza.java

Le constructeur progressif et la pizza sont deux objets différents. Le premier est mutable et temporaire. La seconde copie ses données et devient le résultat utilisé par le programme.

Validation à l'exécution

L'enum Cheese contient aussi des fromages véganes, et build() refuse une pizza végane dont le fromage ne l'est pas. L'appel du début de la leçon, mozzarella puis vegan(), compile donc sans erreur, mais lève une IllegalStateException au moment de build(). La vérification est utile, mais elle arrive tard : à l'exécution.

Factoriser une base fréquente

Un preset prépare une configuration fréquente puis rend le constructeur progressif, pas l'objet final.

Pizza.java

houseStyle() retire la répétition, mais addTopping("olives") reste possible avant build(). Un preset peut aussi partir d'un autre preset : la variante sans gluten remplace seulement ce qui change.

Preset utile

  • • Une base revient souvent.
  • • Chaque appel doit encore pouvoir l'ajuster.
  • • Le nom du preset exprime une intention métier.

Preset trompeur

  • • Il construit immédiatement l'objet final.
  • • Il cache une règle sans nom clair.
  • • Il duplique les mêmes réglages qu'un autre preset.

Retirer l'opération dangereuse

Le constructeur progressif classique rend l'appel lisible, mais il expose toujours cheese(Cheese) après vegan() : l'appel incohérent compile et n'échoue qu'à l'exécution. Le Constructeur par étapes change la forme de l'API après ce choix précis.

PizzaStepBuilder.java
Main.java
  1. vegan() change le type statique

    L'expression ne renvoie plus Builder, mais VeganBuilder.
  2. La branche conserve le vocabulaire utile

    La croûte, les garnitures, la préparation séparée et build() restent disponibles. Le fromage passe par un enum limité aux choix véganes.
  3. La mauvaise opération disparaît

    cheese(Cheese) n'existe pas sur le type retourné. Le compilateur refuse l'appel avant toute exécution.

Pragmatique, pas rigide

Le but n'est pas d'imposer un ordre complet à chaque champ. On retire seulement l'opération qui pose un vrai problème après le choix vegan().
SolutionCe qu'elle garantitPrix
ConstructeurLes types et l'ordre de la signature.Arguments positionnels vite illisibles.
Constructeur progressifOptions nommées, défauts et validation dans build().Un objet et une API temporaires; l'invalide peut compiler.
Constructeur par étapes cibléCertaines opérations disparaissent selon la branche.Types ou wrappers supplémentaires, API plus rigide.

Bon cas

  • • Beaucoup d'options facultatives.
  • • Des arguments positionnels sont ambigus.
  • • Une branche doit rendre certaines opérations impossibles.

Mauvais cas

  • • Un objet stable possède deux champs évidents.
  • • Aucune valeur par défaut ni validation n'existe.
  • • La règle change si souvent que l'API typée devient un fardeau.

Vérifiez vos réflexes

Vérifiez vos réflexes

Une réponse par question. La correction apparaît tout de suite.

1. Pourquoi une méthode fluide retourne-t-elle this ?
2. Où vérifier une combinaison invalide dans un Constructeur progressif classique ?
3. Que doit retourner houseStyle() ?
4. Après vegan(), pourquoi cheese(MOZZARELLA) ne compile-t-il plus ?
5. Quel cas ne mérite probablement pas de Constructeur progressif ?

Encodez une règle dans l'API

Rendez l'appel invalide impossible

Écrivez d'abord. Une IA externe pourra ensuite relire votre travail. Son retour peut être faux. Vérifiez-le avec la leçon.

Le constructeur progressif de Pizza laisse compiler cet appel, qui n'échoue qu'à l'exécution de build() : Pizza.constructeur progressif() .vegan() .cheese(Cheese.MOZZARELLA) .build(); Refactorez uniquement cette branche. 1. vegan() doit retourner un petit type VeganBuilder. 2. VeganBuilder conserve crust(...), addTopping(...), preparedSeparately() et build(). 3. Il expose veganCheese(VeganCheese), mais pas cheese(Cheese). 4. Ne transformez pas chaque champ en étape obligatoire. 5. Ajoutez un preset houseStyle() qui retourne un constructeur progressif prérempli mais encore modifiable. 6. Donnez un appel valide et un appel invalide commenté qui échouerait à la compilation. 7. Expliquez la différence entre une validation dans build() et une garantie portée par le type retourné.

Brouillon enregistré sur cet appareil.

À coller dans l'IA de votre choix.
Voir le prompt qui sera copié
Vous corrigez le travail d'un étudiant qui prépare une remédiation Java 17. Évaluez d'abord son raisonnement, ses invariants et la cohérence de son brouillon. Un code incomplet mais bien expliqué mérite un retour utile. Signalez précisément les erreurs sans inventer d'exigences hors sujet.

Cette leçon porte sur Builder, preset et Step Builder pragmatique en Java. Vérifiez que vegan() change réellement le type statique, que la branche ne propose plus cheese(Cheese), mais conserve les opérations utiles et build(). Le preset doit être composable et ne doit pas construire immédiatement la Pizza. N'exigez pas une interface par champ. Acceptez un wrapper ou quelques interfaces ciblées. Contrôlez surtout la distinction entre erreur à l'exécution dans build() et appel impossible à compiler.

Travail demandé:
Le builder de Pizza laisse compiler cet appel, qui n'échoue qu'à l'exécution de build() :

Pizza.builder()
    .vegan()
    .cheese(Cheese.MOZZARELLA)
    .build();

Refactorez uniquement cette branche.

1. vegan() doit retourner un petit type VeganBuilder.
2. VeganBuilder conserve crust(...), addTopping(...), preparedSeparately() et build().
3. Il expose veganCheese(VeganCheese), mais pas cheese(Cheese).
4. Ne transformez pas chaque champ en étape obligatoire.
5. Ajoutez un preset houseStyle() qui retourne un builder prérempli mais encore modifiable.
6. Donnez un appel valide et un appel invalide commenté qui échouerait à la compilation.
7. Expliquez la différence entre une validation dans build() et une garantie portée par le type retourné.

Commencez par poser au plus deux questions si une intention reste ambiguë. Donnez ensuite les points corrects, les problèmes précis et la plus petite prochaine étape. Ne fournissez une solution complète que si l'étudiant la demande explicitement.

Réponse de l'étudiant:
[aucune réponse collée]

Continuez le parcours

Centre de commandes

Rechercher une page et s'y rendre