![translation](https://cdn.durumis.com/common/trans.png)
Dies ist ein von KI übersetzter Beitrag.
Sprache auswählen
Von durumis AI zusammengefasster Text
- Es werden verschiedene Muster zur effizienten Verwaltung von Konstruktoren mit vielen Parametern vorgestellt, und die Vor- und Nachteile von stufenweisen Konstruktor-Mustern, JavaBeans-Mustern und Builder-Mustern werden verglichen und analysiert.
- Das Builder-Pattern hat den Vorteil, dass es die Lesbarkeit und Stabilität des Codes verbessert und besonders effektiv ist, wenn es mit hierarchisch gestalteten Klassen verwendet wird.
- Das Builder-Pattern ermöglicht es, Code übersichtlicher zu gestalten, hat aber den Nachteil, dass ein Builder-Objekt erstellt werden muss und der Code umständlicher sein kann.
Das Stufenmuster des Konstruktors
Sowohl statische Fabriken als auch Konstruktoren haben Schwierigkeiten, mit vielen Parametern umzugehen. Wenn eine Klasse beispielsweise 6 Felder hat und wir Konstruktoren für 2 Parameter, 3 Parameter, ... erstellen möchten, können wir das Stufenmuster des Konstruktors wie folgt verwenden.
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);
}
Aber selbst dann, wenn viele Parameter vorhanden sind, ist es beim Lesen des Codes verwirrend, was die einzelnen Werte bedeuten, und es kann vorkommen, dass Werte mit dem gleichen Typ verwechselt werden.
Java-Beans-Muster
Das Java-Beans-Muster erstellt Objekte mit einem Konstruktor ohne Parameter und ruft dann Setter-Methoden auf, um die gewünschten Parameterwerte zu setzen.
public class NutritionFactsWithJavaBeansPattern {
private int servingSize = -1; // Erforderlich. Kein Standardwert.
private int servings = -1; // Erforderlich. Kein Standardwert.
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;
}
Das Java-Beans-Muster kann Instanzen auch dann erstellen, wenn viele Parameter vorhanden sind, ohne dass die Werte verwechselt werden. Um jedoch ein Objekt zu erstellen, müssen mehrere Setter-Methoden aufgerufen werden, und bevor das Objekt vollständig abgeschlossen ist, wird die Konsistenz beeinträchtigt. Aus diesem Grund kann die Klasse nicht unveränderlich gemacht werden.
Builder-Muster
Das Builder-Muster, das die Stabilität des Stufenmusters des Konstruktors und die Lesbarkeit des Java-Beans-Musters kombiniert, wird häufig verwendet.
Anstelle der direkten Erstellung des gewünschten Objekts ruft der Client einen Konstruktor mit nur den erforderlichen Parametern auf, um ein Builder-Objekt zu erhalten. Anschließend werden die gewünschten optionalen Parameter mithilfe von Setter-Methoden festgelegt, die vom Builder-Objekt bereitgestellt werden. Schließlich wird die build()-Methode ohne Parameter aufgerufen, um das gewünschte Objekt zu erhalten.
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);
}
}
Der Konstruktor in der Builder-Klasse nimmt nur die erforderlichen Parameter entgegen, während die restlichen optionalen Parameter mithilfe von Setter-Methoden gefüllt werden. Schließlich wird die build()-Methode verwendet, um das vollständige NutritionFactsWithBuilderPattern-Objekt zu erstellen. NutritionFactsWithBuilderPattern ist unveränderlich, und da die Setter-Methoden des Builders den Builder selbst zurückgeben, können sie verkettet aufgerufen werden. Diese Methode wird als Fluent API oder Method Chaining bezeichnet.
NutritionFactsWithBuilderPattern nutritionFacts =
new NutritionFactsWithBuilderPattern.Builder(240, 8)
.calories(100)
.sodium(35)
Aus der Sicht des Clients kann der Code mit dem Builder-Muster einfach geschrieben und gelesen werden.
Das Builder-Muster, das gut zu hierarchisch gestalteten Klassen passt
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();
}
Die Pizza.Builder-Klasse ist ein generischer Typ, der rekursive Typspezifikationen verwendet, und die abstrakte Methode self() wurde hinzugefügt, um eine Methodenverkettung zu ermöglichen, ohne dass Unterklassen Typumwandlungen durchführen müssen. Unterklassen können den Rückgabewert dieser abstrakten Methode einfach auf sich selbst setzen.
Betrachten wir nun die Unterklassen New Yorker Pizza und Calzone-Pizza von Pizza und erleben die Flexibilität des Builder-Musters.
public class NyPizza extends Pizza {
public enum Size {
SMALL, MEDIUM, LARGE
}
private final Size size; // Erforderlicher Parameter
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; // Optionaler Parameter
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;
}
}
Die build()-Klasse, die in der Builder jeder Unterklasse definiert ist, gibt die konkrete Unterklasse zurück. Die Fähigkeit einer Unterklassenmethode, einen Typ zurückzugeben, der nicht der von der Oberklassenmethode zurückgegebene Typ ist, sondern ein Untertyp davon, wird als kovariantes Rückgabetypisieren bezeichnet. Mit dieser Funktion muss der Client keine Typumwandlung durchführen.
NyPizza nyPizza = new NyPizza.Builder(Size.SMALL)
.addTopping(Topping.SAUSAGE)
.addTopping(Topping.ONION)
.build();
CalzonePizza calzonePizza = new CalzonePizza.Builder()
.addTopping(Topping.HAM)
.sauceInside()
Der Client kann das enum von Pizza und das enum jeder Unterklasse mischen und das Objekt mit den entsprechenden Methoden vervollständigen.
Nachteile des Builder-Musters
- Es muss ein Builder-Objekt erstellt werden.
- Der Code ist umfangreich.
Zusammenfassung
Wenn ein Konstruktor oder eine statische Fabrikmethode viele Parameter verarbeiten muss, sollte das Builder-Muster in Betracht gezogen werden.
Quelle
- Effektives Java