![translation](https://cdn.durumis.com/common/trans.png)
Questo è un post tradotto da IA.
[Effective Java] Item 2. Se hai molti parametri nel costruttore, considera di usare un builder
- Lingua di scrittura: Coreana
- •
-
Paese di riferimento: Tutti i paesi
- •
- Tecnologia dell'informazione
Seleziona la lingua
Testo riassunto dall'intelligenza artificiale durumis
- Vengono presentati diversi pattern per gestire in modo efficiente i costruttori con molti parametri, analizzando i pro e i contro del pattern del costruttore graduale, del pattern JavaBeans e del pattern builder.
- Il pattern builder offre i vantaggi di migliorare la leggibilità e la stabilità del codice, ed è particolarmente efficace quando si lavora con classi progettate in modo gerarchico.
- Il pattern builder consente di scrivere codice più chiaro, ma presenta lo svantaggio di richiedere la creazione di un oggetto builder e di rendere il codice più verboso.
Modello di costruttore graduale
Sia le factory statiche che i costruttori hanno difficoltà a gestire in modo appropriato i parametri multipli. Ad esempio, se una classe ha 6 campi e si desidera creare un costruttore per 2 parametri, per 3 parametri, ... ecc., è possibile utilizzare il modello di costruttore graduale, come mostrato di seguito.
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);
}
Tuttavia, anche in questo caso, se il numero di parametri è elevato, il codice potrebbe diventare difficile da leggere e potrebbe essere difficile capire il significato di ciascun valore, potendo inoltre confondere i parametri dello stesso tipo durante l'inserimento dei valori.
Modello di Bean Java
Il modello di Bean Java crea un oggetto utilizzando un costruttore senza parametri e quindi chiama i metodi setter per impostare i valori desiderati dei parametri.
public class NutritionFactsWithJavaBeansPattern {
private int servingSize = -1; // Obbligatorio. Nessun valore predefinito.
private int servings = -1; // Obbligatorio. Nessun valore predefinito.
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;
}
Il modello di Bean Java consente di creare istanze senza confondere i valori, anche se il numero di parametri è elevato. Tuttavia, per creare un oggetto è necessario chiamare più metodi setter e la coerenza potrebbe essere compromessa finché l'oggetto non è completamente completato. Per questo motivo, la classe non può essere resa immutabile.
Modello Builder
Il modello Builder, che combina la sicurezza del modello di costruttore graduale con la leggibilità del modello di Bean Java, è il più utilizzato.
Il client, invece di creare l'oggetto direttamente, chiama il costruttore con solo i parametri obbligatori per ottenere un oggetto Builder. Successivamente, l'oggetto Builder fornisce una sorta di metodi setter per impostare i parametri facoltativi desiderati. Infine, chiama il metodo build() senza parametri per ottenere l'oggetto desiderato.
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);
}
}
Il costruttore all'interno della classe Builder accetta solo i parametri obbligatori, mentre gli altri parametri facoltativi vengono riempiti utilizzando una sorta di metodi setter. Infine, il metodo build() crea l'oggetto NutritionFactsWithBuilderPattern completo. La classe NutritionFactsWithBuilderPattern è immutabile e i metodi setter del builder restituiscono il builder stesso, consentendo chiamate a catena. Questo approccio è chiamato API fluente o concatenazione di metodi.
NutritionFactsWithBuilderPattern nutritionFacts =
new NutritionFactsWithBuilderPattern.Builder(240, 8)
.calories(100)
.sodium(35)
Dal punto di vista del client, il modello Builder semplifica la scrittura e la lettura del codice.
Modello Builder adatto a classi progettate gerarchicamente
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 è un tipo generico che utilizza una limitazione di tipo ricorsiva e, grazie al metodo astratto self(), supporta la concatenazione di metodi senza dover eseguire alcuna conversione di tipo nelle sottoclassi. Nelle sottoclassi, è sufficiente impostare il valore restituito di questo metodo astratto su se stesso.
Esaminiamo ora le sottoclassi di Pizza, la pizza di New York e la pizza Calzone, per sperimentare la flessibilità del modello Builder.
public class NyPizza extends Pizza {
public enum Size {
SMALL, MEDIUM, LARGE
}
private final Size size; // Parametro obbligatorio
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; // Parametro facoltativo
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;
}
}
Il metodo build() definito dai builder di ciascuna sottoclasse restituisce la sottoclasse concreta. La capacità di un metodo di una sottoclasse di restituire un tipo che non è il tipo restituito dal metodo della superclasse, ma un suo sottotipo, è chiamata tipizzazione covariante dei valori restituiti. Grazie a questa funzione, il client non deve eseguire conversioni di tipo.
NyPizza nyPizza = new NyPizza.Builder(Size.SMALL)
.addTopping(Topping.SAUSAGE)
.addTopping(Topping.ONION)
.build();
CalzonePizza calzonePizza = new CalzonePizza.Builder()
.addTopping(Topping.HAM)
.sauceInside()
Dal punto di vista del client, è possibile utilizzare l'enumerazione di Pizza e l'enumerazione di ciascuna sottoclasse in modo combinato e completare l'oggetto utilizzando i metodi appropriati di ciascuna.
Svantaggi del modello Builder
- È necessario creare un oggetto Builder.
- Il codice può essere prolisso.
Riassunto
Se i costruttori o i metodi di factory statici devono gestire un gran numero di parametri, prendi in considerazione il modello Builder.
Fonte
- Java Efficace