Presente este artículo del Dr. Dobbs , y el Patrón del constructor en particular, ¿cómo manejamos el caso de subclasificar un Constructor? Tomando una versión reducida del ejemplo donde queremos subclasificar para agregar el etiquetado de OGM, una implementación ingenua sería:
public class NutritionFacts {
private final int calories;
public static class Builder {
private int calories = 0;
public Builder() {}
public Builder calories(int val) { calories = val; return this; }
public NutritionFacts build() { return new NutritionFacts(this); }
}
protected NutritionFacts(Builder builder) {
calories = builder.calories;
}
}
Subclase:
public class GMOFacts extends NutritionFacts {
private final boolean hasGMO;
public static class Builder extends NutritionFacts.Builder {
private boolean hasGMO = false;
public Builder() {}
public Builder GMO(boolean val) { hasGMO = val; return this; }
public GMOFacts build() { return new GMOFacts(this); }
}
protected GMOFacts(Builder builder) {
super(builder);
hasGMO = builder.hasGMO;
}
}
Ahora, podemos escribir código como este:
GMOFacts.Builder b = new GMOFacts.Builder();
b.GMO(true).calories(100);
Pero, si nos equivocamos en el pedido, todo falla:
GMOFacts.Builder b = new GMOFacts.Builder();
b.calories(100).GMO(true);
El problema es, por supuesto, que NutritionFacts.Builder
devuelve un NutritionFacts.Builder
, no un GMOFacts.Builder
, entonces, ¿cómo resolvemos este problema, o hay un mejor patrón para usar?
Nota: esta respuesta a una pregunta similar ofrece las clases que tengo arriba; Mi pregunta es sobre el problema de garantizar que las llamadas del constructor estén en el orden correcto.
fuente
build()
la salida deb.GMO(true).calories(100)
?Respuestas:
Puedes resolverlo usando genéricos. Creo que esto se llama los "patrones genéricos curiosamente recurrentes"
Haga que el tipo de retorno de los métodos del generador de clases base sea un argumento genérico.
Ahora cree una instancia del generador base con el generador de clases derivado como argumento genérico.
fuente
implements
lugar deextends
, o (c) tirar todo a la basura. Ahora tengo un extraño error de compilación dondeleafBuilder.leaf().leaf()
yleafBuilder.mid().leaf()
está bien, peroleafBuilder.leaf().mid().leaf()
falla ...return (T) this;
da como resultado unaunchecked or unsafe operations
advertencia. Esto es imposible de evitar, ¿verdad?unchecked cast
advertencia, vea la solución sugerida a continuación entre las otras respuestas: stackoverflow.com/a/34741836/3114959Builder<T extends Builder>
realidad es un tipo sin formato , esto debería serBuilder<T extends Builder<T>>
.Builder
forGMOFacts
también debe ser genéricoBuilder<B extends Builder<B>> extends NutritionFacts.Builder<Builder>
, y este patrón puede continuar tantos niveles como sea necesario. Si declara un constructor no genérico, no puede extender el patrón.Solo para el registro, para deshacerse de la
para la
return (T) this;
declaración de la que hablan @dimadima y @Thomas N., la siguiente solución se aplica en ciertos casos.Cree
abstract
el generador que declara el tipo genérico (T extends Builder
en este caso) y declareprotected abstract T getThis()
el método abstracto de la siguiente manera:Consulte http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205 para obtener más detalles.
fuente
build()
método devuelve los Datos de Nutrición aquí?public GMOFacts build() { return new GMOFacts(this); }
BuilderC extends BuilderB
yBuilderB extends BuilderA
cuandoBuilderB
no lo esabstract
Basado en una publicación de blog , este enfoque requiere que todas las clases no hoja sean abstractas, y todas las clases hoja deben ser finales.
Luego, tiene alguna clase intermedia que extiende esta clase y su generador, y tantas más como necesite:
Y, finalmente, una clase de hoja concreta que puede llamar a todos los métodos de construcción en cualquiera de sus padres en cualquier orden:
Luego, puede llamar a los métodos en cualquier orden, desde cualquiera de las clases en la jerarquía:
fuente
B
, siempre resulta ser la clase base.<T extends SomeClass, B extends SomeClass.Builder<T,B>> extends SomeClassParent.Builder<T,B>
patrón que la clase SecondLevel intermedia, sino que declara tipos específicos. No puede instalar una clase hasta que llegue a la hoja usando los tipos específicos, pero una vez que lo haga, no puede extenderla más porque está usando los tipos específicos y ha abandonado el Patrón de plantilla curiosamente recurrente. Este enlace podría ayudar: angelikalanger.com/GenericsFAQ/FAQSections/…También puede anular el
calories()
método y dejar que devuelva el generador de extensión. Esto se compila porque Java admite tipos de retorno covariantes .fuente
También hay otra forma de crear clases de acuerdo con el
Builder
patrón, que se ajusta a "Preferir composición sobre herencia".Defina una interfaz, esa clase padre
Builder
heredará:La implementación de
NutritionFacts
es casi la misma (excepto paraBuilder
implementar la interfaz 'FactsBuilder'):El
Builder
de una clase secundaria debería extender la misma interfaz (excepto una implementación genérica diferente):Tenga en cuenta que
NutritionFacts.Builder
es un campo dentroGMOFacts.Builder
(llamadobaseBuilder
). El método implementado desde el método deFactsBuilder
llamadas de interfazbaseBuilder
del mismo nombre:También hay un gran cambio en el constructor de
GMOFacts(Builder builder)
. La primera llamada en el constructor al constructor de la clase padre debe pasar apropiadoNutritionFacts.Builder
:La implementación completa de la
GMOFacts
clase:fuente
Un ejemplo completo de 3 niveles de herencia de constructor múltiple se vería así :
(Para la versión con un constructor de copia para el generador, vea el segundo ejemplo a continuación)
Primer nivel - padre (potencialmente abstracto)
Segundo nivel
Tercer nivel
Y un ejemplo de uso
Una versión un poco más larga con un constructor de copia para el constructor:
Primer nivel - padre (potencialmente abstracto)
Segundo nivel
Tercer nivel
Y un ejemplo de uso
fuente
Si no quieres enfocarte en un par de ángulos o tres, o tal vez no te sientas ... umm ... quiero decir ... tos ... el resto de tu equipo comprenderá rápidamente con curiosidad patrón genérico recurrente, puede hacer esto:
Apoyado por
y el tipo principal:
Puntos clave:
EDITAR:
Encontré una forma de evitar la creación de objetos espurios. Primero agregue esto a cada constructor:
Luego, en el constructor para cada constructor:
El costo es un archivo de clase adicional para la
new Object(){}
clase interna anónimafuente
Una cosa que podría hacer es crear un método de fábrica estático en cada una de sus clases:
Este método de fábrica estático devolvería el generador apropiado. Puede tener una
GMOFacts.Builder
extensión aNutritionFacts.Builder
, eso no es un problema. El problema aquí será lidiar con la visibilidad ...fuente
La siguiente contribución de IEEE Refined Fluent Builder en Java brinda una solución integral al problema.
Disecciona la pregunta original en dos subproblemas de deficiencia de herencia y cuasi invariancia y muestra cómo se abre una solución a estos dos subproblemas para el soporte de herencia con la reutilización de código en el patrón clásico de construcción en Java.
fuente
Creé una clase de generador genérica abstracta y principal que acepta dos parámetros de tipo formal. El primero es para el tipo de objeto devuelto por build (), el segundo es el tipo devuelto por cada configurador de parámetros opcional. A continuación se encuentran las clases para padres e hijos con fines ilustrativos:
Este ha satisfecho mis necesidades a satisfacción.
fuente