java - pattern - El patrón de generador y una gran cantidad de parámetros obligatorios
java builder pattern (5)
He estado luchando últimamente para entender cómo el patrón es de beneficio cuando todos tus parámetros son obligatorios
El patrón facilita la creación de clases inmutables y promueve el código legible. Considere la clase Person a continuación (con un constructor convencional y un constructor).
public static class Person {
private static final class Builder {
private int height, weight, age, income, rank;
public Builder setHeight(final int height) { this.height = height; return this; }
public Builder setWeight(final int weight) { this.weight = weight; return this; }
public Builder setAge(final int age) { this.age = age; return this; }
public Builder setIncome(final int income) { this.income = income; return this; }
public Builder setRank(final int rank) { this.rank = rank; return this; }
public Person build() { return new Person(this); }
}
private final int height;
private final int weight;
private final int age;
private final int income;
private final int rank;
public Person(final int height, final int weight, final int age, final int income, final int rank) {
this.height = height; this.weight = weight; this.age = age; this.income = income; this.rank = rank;
}
private Person(final Builder builder) {
height = builder.height; weight = builder.weight; age = builder.age; income = builder.income; rank = builder.rank;
// Perform validation
}
public int getHeight() { return height; }
public int getWeight() { return weight; }
public int getAge() { return age; }
public int getIncome() { return income; }
public int getRank() { return rank; }
}
¿Qué método de construcción es más fácil de comprender?
final Person p1 = new Person(163, 184, 48, 15000, 23);
final Person p2 = new Person.Builder().setHeight(163).setWeight(184).setAge(48).
setIncome(15000).setRank(23).build();
Una forma de evitar esto ha sido agrupar de forma lógica los parámetros que se pasan a sus propias clases
Claro, este es el principio de cohesion y debe ser adoptado independientemente de la semántica de construcción del objeto.
Hasta la fecha, uso la following implementación del patrón de generador (a diferencia de la implementación que se describe here ):
public class Widget {
public static class Builder {
public Builder(String name, double price) { ... }
public Widget build() { ... }
public Builder manufacturer(String value) { ... }
public Builder serialNumber(String value) { ... }
public Builder model(String value) { ... }
}
private Widget(Builder builder) { ... }
}
Esto funciona bien para la mayoría de las situaciones que he encontrado donde necesito construir un objeto complejo con una variedad de parámetros requeridos / obligatorios y opcionales. Sin embargo, he estado luchando últimamente para comprender cómo el patrón es beneficioso cuando todos sus parámetros son obligatorios (o al menos la gran mayoría lo son).
Una forma de evitar esto ha sido agrupar de forma lógica los parámetros que se pasan a sus propias clases para reducir el número de parámetros que se pasan al constructor del constructor.
Por ejemplo, en lugar de:
Widget example = new Widget.Builder(req1, req2, req3,req4,req5,req6,req7,req8)
.addOptional(opt9)
.build();
se agrupa de la siguiente manera:
Object1 group1 = new Object1(req1, req2, req3, req4);
Object2 group2 = new Object2(req5, req6);
Widget example2 = new Widget.Builder(group1, group2, req7, req8)
.addOptional(opt9)
.build();
Si bien tener objetos separados simplifica bastante las cosas, también dificulta un poco las cosas si uno no está familiarizado con el código. Una cosa que consideré fue mover todos los parámetros en sus propios addParam(param)
y luego realizar la validación de los parámetros requeridos en el método build()
.
¿Cuál es la mejor práctica y hay quizás un mejor enfoque de esto que no he considerado?
Sin embargo, he estado luchando últimamente para comprender cómo el patrón es beneficioso cuando todos sus parámetros son obligatorios (o al menos la gran mayoría lo son).
El patrón de generador fluido sigue siendo beneficioso:
Es más legible: permite efectivamente parámetros con nombre para que la llamada no sea solo una larga lista de argumentos sin nombre
Es desordenado: esto le permite agrupar argumentos en grupos lógicos, ya sea como parte de una única llamada a un generador de compilación o simplemente permitiéndole usar un orden natural para llamar a los métodos de creación de compiladores que tengan más sentido para esta instanciación particular.
Widget example = new Widget.Builder(req1, req2, req3,req4,req5,req6,req7,req8) .addOptional(opt9) .build();
se agrupa de la siguiente manera:
Object1 group1 = new Object1(req1, req2, req3, req4); Object2 group2 = new Object2(req5, req6); Widget example2 = new Widget.Builder(group1, group2, req7, req8) .addOptional(opt9) .build();
Si bien tener objetos separados simplifica bastante las cosas, también dificulta un poco las cosas si uno no está familiarizado con el código. Una cosa que consideré fue mover todos los parámetros en sus propios
addParam(param)
y luego realizar la validación de los parámetros requeridos en el métodobuild()
.
Yo preferiría un híbrido cuando sea apropiado o natural. No tiene que ser todo en constructor o cada parámetro tiene su propio método addParam. El generador le da flexibilidad para hacer uno, el otro, en el medio, o un combo:
Widget.Builder builder = new Widget.Builder(Widget.BUTTON); builder.withWidgetBackingService(url, resource, id); builder.withWidgetStyle(bgColor, lineWidth, fontStyle); builder.withMouseover("Not required"); Widget example = builder.build();
Puede usar un Step Builder si tiene muchos parámetros obligatorios. En resumen: usted define una interfaz para cada parámetro obligatorio y un método de construcción devuelve la siguiente interfaz de constructor obligatoria o el propio constructor para los métodos opcionales. El constructor sigue siendo una clase única que implementa todas las interfaces.
Los idiomas como Kotlin y Scala son más convenientes aquí, ya que ofrecen parámetros con nombre con valores predeterminados.
Un generador / fábrica todavía le permite desacoplar la interfaz del tipo de implementación (o le permite enchufar un adaptador, etc.), suponiendo que Widget
convierta en una interfaz y usted tiene una forma de inyectar u ocultar el new Widget.Builder
.
Si no te importa el desacoplamiento, y tu implementación es única, entonces tienes razón: el patrón del constructor no es mucho más útil que un constructor normal (todavía etiqueta sus argumentos con el atributo por constructor). -metodo de estilo.)
Si está creando objetos repetidamente con poca variación en los argumentos, aún puede ser útil. Podría ingresar, caché, etc. el constructor intermedio adquirido después de conectar varios atributos:
Widget.Builder base = new Widget.Builder(name, price).model("foo").manufacturer("baz");
// ...
Widget w1 = base.serialNumber("bar").build();
Widget w2 = base.serialNumber("baz").build();
Widget w3 = base.serialNumber("quux").build();
Esto supone que sus constructores son inmutables: los instaladores de constructores no establecen un atributo y lo devuelven, sino que devuelven una nueva copia de ellos mismos con el cambio. Como señaló anteriormente, los objetos de parámetros son otra forma de evitar el repetitivo argumento repetitivo. Allí, ni siquiera necesita el patrón de construcción: simplemente pase el objeto de parámetro a su constructor de implementación.
Una ventaja del patrón de generador que rara vez (si es que alguna vez) se ve promocionado es que también se puede usar para construir condicionalmente el objeto, por ejemplo, solo si todos los parámetros obligatorios son correctos o si hay otros recursos necesarios disponibles. En ese sentido, ofrecen beneficios similares a un método de fábrica estático .