To jest post przetłumaczony przez AI.
Wybierz język
Tekst podsumowany przez sztuczną inteligencję durumis
- Prezentuje różne wzorce służące do efektywnego zarządzania konstruktorami z wieloma parametrami, porównując zalety i wady wzorca konstruktora stopniowego, wzorca fasoli Java i wzorca buildera.
- Wzorzec buildera ma tę zaletę, że zwiększa czytelność i stabilność kodu, a jego użycie jest szczególnie efektywne w przypadku klas zaprojektowanych hierarchicznie.
- Wzorzec buildera pozwala na bardziej przejrzyste pisanie kodu, ale ma wadę w postaci tworzenia obiektu buildera i zwiększania rozwleklego charakteru kodu.
Wzór projektowy budowniczego
Zarówno statyczne fabryki, jak i konstruktory mają problemy z obsługą dużej liczby parametrów. Na przykład, jeśli klasa ma 6 pól i chcemy utworzyć konstruktor dla 2 parametrów, 3 parametrów itd., możemy wykorzystać wzorzec projektowy budowniczego, jak pokazano poniżej.
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);
}
Jednak nawet w tym przypadku, gdy liczba parametrów jest duża, kod może być trudny do odczytania, a wartości parametrów mogą być mylące, zwłaszcza gdy parametry mają ten sam typ.
Wzorzec projektowy JavaBeans
Wzorzec projektowy JavaBeans polega na tworzeniu obiektu za pomocą konstruktora bez parametrów, a następnie ustawianiu wartości żądanych parametrów za pomocą metod setter.
public class NutritionFactsWithJavaBeansPattern {
private int servingSize = -1; // wymagane. Brak wartości domyślnej.
private int servings = -1; // wymagane. Brak wartości domyślnej.
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;
}
Wzorzec projektowy JavaBeans pozwala na tworzenie instancji obiektu nawet w przypadku dużej liczby parametrów, bez ryzyka ich pomieszania. Jednak utworzenie jednego obiektu wymaga wywołania wielu metod setter, a obiekty nie są spójne, dopóki nie zostaną w pełni utworzone. Z tego powodu klas nie można uczynić niezmiennymi.
Wzorzec projektowy budowniczego
Wzorzec projektowy budowniczego łączy w sobie zalety wzorca projektowego wzrastających konstruktorów i wzorca projektowego JavaBeans.
Klient zamiast tworzyć obiekt bezpośrednio, wywołuje konstruktor z wymaganymi parametrami, aby uzyskać obiekt typu budowniczego. Następnie ustawia wartości opcjonalnych parametrów za pomocą metod setter dostępnych w obiekcie typu budowniczego. Na koniec wywołuje metodę build() bez parametrów, aby uzyskać żądany obiekt.
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);
}
}
Konstruktor klasy Builder przyjmuje tylko wymagane parametry, a pozostałe opcjonalne parametry są ustawiane za pomocą metod setter. Na koniec metoda build() tworzy pełny obiekt NutritionFactsWithBuilderPattern. Klasa NutritionFactsWithBuilderPattern jest niezmienna, a metody setter klasy Builder zwracają samą siebie, co pozwala na wywoływanie ich w łańcuchu. Ten sposób nazywa się płynnym API lub łańcuchem metod.
NutritionFactsWithBuilderPattern nutritionFacts =
new NutritionFactsWithBuilderPattern.Builder(240, 8)
.calories(100)
.sodium(35)
Z punktu widzenia klienta wzorzec projektowy budowniczego pozwala na łatwe i czytelne pisanie kodu.
Wzorzec projektowy budowniczego dobrze komponuje się z klasami hierarchicznymi
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();
}
Klasa Pizza.Builder jest klasą generyczną z wykorzystaniem rekurencyjnego ograniczenia typu, a abstrakcyjna metoda self() pozwala na obsługę łańcucha metod bez rzutowania w klasach podrzędnych. W klasach podrzędnych należy ustawić wartość zwracaną tej abstrakcyjnej metody na samą siebie.
Spójrzmy teraz na podklasy Pizza: Pizza nowojorska i Calzone, aby zobaczyć elastyczność wzorca projektowego budowniczego.
public class NyPizza extends Pizza {
public enum Size {
SMALL, MEDIUM, LARGE
}
private final Size size; // wymagany parametr
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; // opcjonalny parametr
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;
}
}
Metoda build() zdefiniowana w konstruktorze każdej podklasy zwraca konkretną podklasę. Ta funkcja, w której metoda podklasy zwraca typ podklasy, a nie typ nadrzędny, jest nazywana kowariantnym typowanym zwracaniem. Dzięki temu klienci nie muszą rzutować.
NyPizza nyPizza = new NyPizza.Builder(Size.SMALL)
.addTopping(Topping.SAUSAGE)
.addTopping(Topping.ONION)
.build();
CalzonePizza calzonePizza = new CalzonePizza.Builder()
.addTopping(Topping.HAM)
.sauceInside()
Klienci mogą łączyć ze sobą wyliczenia Pizza i wyliczenia każdej podklasy, a następnie używać odpowiednich metod, aby stworzyć pełny obiekt.
Wady wzorca projektowego budowniczego
- Konieczność utworzenia obiektu typu budowniczego.
- Kod może być rozwlekły.
Podsumowanie
Jeśli konstruktor lub statyczna metoda fabryczna musi obsługiwać dużą liczbę parametrów, rozważ użycie wzorca projektowego budowniczego.
Źródła
- Effective Java