![translation](https://cdn.durumis.com/common/trans.png)
Esta es una publicación traducida por IA.
[Effective Java] Item 2. Considere un constructor si el constructor tiene muchos parámetros
- Idioma de escritura: Coreano
- •
-
País de referencia: Todos los países
- •
- Tecnología de la información
Seleccionar idioma
Texto resumido por la IA durumis
- Presenta varios patrones para administrar eficientemente los constructores con muchos parámetros y analiza las ventajas y desventajas de los patrones de constructor escalonado, Java Beans y constructor.
- El patrón de constructor tiene la ventaja de mejorar la legibilidad y la estabilidad del código, y es particularmente eficaz cuando se utiliza con clases diseñadas jerárquicamente.
- El patrón de constructor permite escribir código de manera más clara, pero tiene la desventaja de crear objetos de constructor y código prolijo.
Patrón de constructor incremental
Las fábricas estáticas y los constructores tienen dificultades para manejar adecuadamente una gran cantidad de parámetros. Por ejemplo, si una clase tiene 6 campos y queremos separar los constructores para 2, 3, ... parámetros, podemos usar el patrón de constructor incremental como se muestra a continuación.
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);
}
Sin embargo, incluso con esto, si hay muchos parámetros, el código puede ser difícil de leer, ya que puede ser difícil recordar el significado de cada valor, y los parámetros del mismo tipo pueden confundirse y asignarse valores incorrectos.
Patrón de JavaBeans
El patrón de JavaBeans crea objetos con un constructor sin parámetros y luego utiliza los métodos setter para establecer los valores de los parámetros deseados.
public class NutritionFactsWithJavaBeansPattern {
private int servingSize = -1; // Obligatorio. Sin valor predeterminado.
private int servings = -1; // Obligatorio. Sin valor predeterminado.
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;
}
El patrón de JavaBeans permite crear instancias sin confusión incluso con muchos parámetros. Sin embargo, se requieren varias llamadas a métodos setter para crear un objeto, y la coherencia se pierde hasta que el objeto está completamente construido. Por lo tanto, las clases no pueden hacerse inmutables.
Patrón de constructor
El patrón de constructor, que combina la estabilidad del patrón de constructor incremental y la legibilidad del patrón de JavaBeans, se utiliza principalmente.
En lugar de crear el objeto directamente, el cliente llama al constructor con solo los parámetros obligatorios para obtener un objeto constructor. Luego, utiliza los métodos setter proporcionados por el objeto constructor para establecer los parámetros opcionales deseados. Finalmente, llama al método build(), que no tiene parámetros, para obtener el objeto deseado.
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);
}
}
El constructor dentro de la clase Builder solo toma los parámetros obligatorios, y los demás parámetros opcionales se rellenan mediante un tipo de método setter. Finalmente, el método build() crea el objeto NutritionFactsWithBuilderPattern completado. La clase NutritionFactsWithBuilderPattern es inmutable, y los métodos setter del constructor devuelven el constructor mismo, por lo que pueden ser llamados en cadena. Este enfoque se conoce como API fluida o encadenamiento de métodos.
NutritionFactsWithBuilderPattern nutritionFacts =
new NutritionFactsWithBuilderPattern.Builder(240, 8)
.calories(100)
.sodium(35)
Desde el punto de vista del cliente, el patrón de constructor permite escribir y leer el código de forma fácil y sencilla.
El patrón de constructor funciona bien con clases jerárquicas
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 clase Pizza.Builder es un tipo genérico que utiliza la limitación de tipo recursivo, y el método abstracto self() añadido permite que las subclases admitan el encadenamiento de métodos sin necesidad de conversión de tipo. Las subclases solo tienen que devolver su propia instancia en este método abstracto.
Ahora, veamos las subclases de Pizza, New York Pizza y Calzone Pizza, para experimentar la flexibilidad del patrón de constructor.
public class NyPizza extends Pizza {
public enum Size {
SMALL, MEDIUM, LARGE
}
private final Size size; // Parámetro obligatorio
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; // Parámetro opcional
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;
}
}
El método build() definido en el constructor de cada subclase devuelve la subclase específica. La capacidad de que los métodos de las subclases devuelvan un tipo que no sea el tipo devuelto por los métodos de la superclase, sino un tipo inferior, se denomina escritura de tipo de retorno covariante. Al utilizar esta característica, el cliente no necesita realizar conversiones de tipo.
NyPizza nyPizza = new NyPizza.Builder(Size.SMALL)
.addTopping(Topping.SAUSAGE)
.addTopping(Topping.ONION)
.build();
CalzonePizza calzonePizza = new CalzonePizza.Builder()
.addTopping(Topping.HAM)
.sauceInside()
Desde la perspectiva del cliente, se puede utilizar la enumeración de Pizza y la enumeración de cada subclase juntas, y el objeto se puede completar utilizando los métodos apropiados para cada uno.
Desventajas del patrón de constructor
- Hay que crear un objeto constructor.
- El código es verboso.
Resumen
Si el constructor o el método de fábrica estático tiene muchos parámetros para manejar, considera el patrón de constructor.
Fuente
- Effective Java