![translation](https://cdn.durumis.com/common/trans.png)
Ceci est un post traduit par IA.
[Effective Java] Item 2. Si vous avez beaucoup de paramètres dans le constructeur, envisagez d'utiliser un générateur
- Langue de rédaction : Coréen
- •
-
Pays de référence : Tous les pays
- •
- Technologies de l'information
Choisir la langue
Texte résumé par l'IA durumis
- Il présente divers modèles pour gérer efficacement les constructeurs avec un grand nombre de paramètres, et compare les avantages et les inconvénients des modèles de constructeurs graduels, des modèles de beans Java et des modèles de générateurs.
- Le modèle de générateur présente l'avantage d'améliorer la lisibilité et la stabilité du code, et il est encore plus efficace lorsqu'il est utilisé avec des classes conçues de manière hiérarchique.
- Le modèle de générateur permet d'écrire du code plus clair, mais présente l'inconvénient de créer des objets générateurs et d'alourdir le code.
Modèle de constructeur incrémental
Les usines statiques et les constructeurs ont tous deux du mal à faire face correctement à un grand nombre de paramètres. Par exemple, si la classe a 6 champs et que vous souhaitez diviser le constructeur en cas de 2 paramètres, 3 paramètres, ..., vous pouvez utiliser le modèle de constructeur incrémental comme indiqué ci-dessous.
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}
public NutritionFacts(int servingSize, int servings) {
this(servingSize, servings, 0, 0, 0, 0);
}
public NutritionFacts(int servingSize, int servings, int calories) {
this(servingSize, servings, calories, 0, 0, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat) {
this(servingSize, servings, calories, fat, 0, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
this(servingSize, servings, calories, fat, sodium, 0);
}
Mais même cela, si le nombre de paramètres augmente, le code devient difficile à lire car la signification de chaque valeur est confuse et les paramètres du même type peuvent être confondus lors de l'entrée de valeurs.
Modèle de Beans Java
Le modèle de Beans Java crée un objet avec un constructeur sans paramètre, puis appelle des méthodes setter pour définir les valeurs des paramètres souhaités.
public class NutritionFactsWithJavaBeansPattern {
private int servingSize = -1; // Obligatoire. Pas de valeur par défaut.
private int servings = -1; // Obligatoire. Pas de valeur par défaut.
private int calories;
private int fat;
private int sodium;
private int carbohydrate;
public void setServingSize(int servingSize) {
this.servingSize = servingSize;
}
public void setServings(int servings) {
this.servings = servings;
}
public void setCalories(int calories) {
this.calories = calories;
}
public void setFat(int fat) {
this.fat = fat;
}
public void setSodium(int sodium) {
this.sodium = sodium;
}
public void setCarbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
}
Le modèle de Beans Java peut créer des instances sans confusion de valeurs, même si le nombre de paramètres augmente. Cependant, la création d'un objet nécessite plusieurs appels de méthodes setter et la cohérence est rompue jusqu'à ce que l'objet soit complètement terminé. Pour cette raison, la classe ne peut pas être rendue immuable.
Modèle de constructeur
Le modèle de constructeur, qui combine la sécurité du modèle de constructeur incrémental et la lisibilité du modèle de Beans Java, est généralement utilisé.
Au lieu de créer directement l'objet souhaité, le client appelle le constructeur avec les paramètres obligatoires pour obtenir un objet constructeur. Ensuite, l'objet constructeur fournit une sorte de méthodes setter pour définir les paramètres optionnels souhaités. Enfin, il appelle la méthode build() sans paramètre pour obtenir l'objet souhaité.
public class NutritionFactsWithBuilderPattern {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
private NutritionFactsWithBuilderPattern(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
public static class Builder {
private final int servingSize;
private final int servings;
private int calories;
private int fat;
private int sodium;
private int carbohydrate;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val) {
calories = val;
return this;
}
public Builder fat(int val) {
fat = val;
return this;
}
public Builder sodium(int val) {
sodium = val;
return this;
}
public Builder carbohydrate(int val) {
calories = val;
return this;
}
public NutritionFactsWithBuilderPattern build() {
return new NutritionFactsWithBuilderPattern(this);
}
}
Le constructeur de la classe Builder accepte uniquement les paramètres obligatoires et les autres paramètres optionnels sont remplis par une sorte de méthodes setter. Enfin, la méthode build() crée l'objet NutritionFactsWithBuilderPattern complet. NutritionFactsWithBuilderPattern est immuable et les méthodes setter du constructeur renvoient le constructeur lui-même, ce qui permet des appels en chaîne. Cette approche est appelée API fluide ou chaînage de méthodes.
NutritionFactsWithBuilderPattern nutritionFacts =
new NutritionFactsWithBuilderPattern.Builder(240, 8)
.calories(100)
.sodium(35)
Du point de vue du client, le modèle de constructeur permet d'écrire et de lire facilement le code.
Modèle de constructeur bien adapté aux classes hiérarchiques
public abstract class Pizza {
public enum Topping {
HAM, MUSHROOM, ONION, PEPPER, SAUSAGE
}
final Set toppings;
Pizza(Builder> builder) {
toppings = builder.toppings.clone();
}
abstract static class Builder> {
private EnumSet toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
toppings.add(topping);
return self();
}
abstract Pizza build();
protected abstract T self();
}
La classe Pizza.Builder est un type générique utilisant une limitation de type récursive et la méthode abstraite self() est ajoutée pour permettre le chaînage de méthodes sans conversion de type dans les sous-classes. Dans les sous-classes, la valeur de retour de cette méthode abstraite doit être elle-même.
Examinons maintenant la pizza new-yorkaise et la calzone, qui sont des sous-classes de Pizza, pour découvrir la flexibilité du modèle de constructeur.
public class NyPizza extends Pizza {
public enum Size {
SMALL, MEDIUM, LARGE
}
private final Size size; // Paramètre obligatoire
private NyPizza(Builder builder) {
super(builder);
size = builder.size;
}
public static class Builder extends Pizza.Builder {
private final Size size;
public Builder(Size size) {
this.size = size;
}
@Override
NyPizza build() {
return new NyPizza(this);
}
@Override
protected Builder self() {
return this;
}
}
}
public class CalzonePizza extends Pizza {
private final boolean sauceInside; // Paramètre optionnel
private CalzonePizza(Builder builder) {
super(builder);
sauceInside = builder.sauceInside;
}
public static class Builder extends Pizza.Builder {
private boolean sauceInside = false;
public Builder sauceInside() {
sauceInside = true;
return this;
}
@Override
CalzonePizza build() {
return new CalzonePizza(this);
}
@Override
protected Builder self() {
return this;
}
}
La classe build() définie par le constructeur de chaque sous-classe renvoie la sous-classe concrète. Le fait qu'une méthode de la sous-classe renvoie un type inférieur à celui renvoyé par la méthode de la super-classe est appelé typage de retour covariant. Grâce à cette fonctionnalité, le client n'a pas besoin de convertir le type.
NyPizza nyPizza = new NyPizza.Builder(Size.SMALL)
.addTopping(Topping.SAUSAGE)
.addTopping(Topping.ONION)
.build();
CalzonePizza calzonePizza = new CalzonePizza.Builder()
.addTopping(Topping.HAM)
.sauceInside()
Du point de vue du client, il est possible de mélanger l'énumération de Pizza et l'énumération de chaque sous-classe et de compléter l'objet avec les méthodes appropriées.
Inconvénients du modèle de constructeur
- Il faut créer un objet constructeur.
- Le code est prolixe.
Résumé
Si le constructeur ou la méthode d'usine statique doit gérer un grand nombre de paramètres, envisagez le modèle de constructeur.
Sources
- Effective Java