sencillos - Patrón de generador para la jerarquía de objetos polimórficos: ¿posible con Java?
tipos de polimorfismo (5)
Tengo una jerarquía de interfaces, con Child
implementando Parent
. Me gustaría trabajar con objetos inmutables, por lo que me gustaría diseñar clases de Builder
que construyan estos objetos de manera conveniente. Sin embargo, tengo muchas interfaces secundarias y no quiero repetir el código para crear Parent
s en cada tipo de constructor secundario.
Entonces, asume las siguientes definiciones:
public interface Parent {
public Long getParentProperty();
}
public interface Child1 extends Parent {
public Integer getChild1Property();
}
public interface Child2 extends Parent {
public String getChild2PropertyA();
public Object getChild2PropertyB();
}
¿Cómo puedo implementar eficientemente los constructores Child1Builder
y Child2Builder
? Deben apoyar la operación como:
Child1 child1 = Child1Builder.newChild1().withChild1Property(5).withParentProperty(10L);
y
Child2 child2 = Child2Builder.newChild2().withChild2PropertyA("Hello").withParentProperty(10L).withChild2PropertyB(new Object());
No quiero implementar un caso especial de withParentProperty
de withParentProperty
para cada constructor hijo.
Editado para agregar una segunda propiedad a Child2
para aclarar que esto no se puede hacer con genéricos simples. No estoy buscando una manera de combinar Child1
y Child2
- Estoy buscando una manera de implementar un sistema Builder
que no duplique el trabajo de crear la clase principal para cada clase secundaria.
¡Gracias por cualquier ayuda!
La solución que imagino es como el patrón de plantilla curiosamente recurrente , o CRTP . Puede definir una clase base para manejar la inicialización relacionada con los padres, pero aún puede encontrar que los dos getParent()
y getThis()
son demasiada repetición en cada clase de constructor derivada relacionada con el niño.
Echar un vistazo:
abstract class ParentBase implements Parent
{
@Override
public final Long getParentProperty()
{
return parentProperty_;
}
protected void setParentProperty(Long value)
{
parentProperty_ = value;
}
private Long parentProperty_;
}
abstract class ParentBuilder<T extends ParentBuilder<T>>
{
T withParentProperty(Long value)
{
getParent().setParentProperty(value);
return getThis();
}
protected abstract ParentBase getParent();
protected abstract T getThis();
}
final class ConcreteChild1 extends ParentBase implements Child1
{
@Override
public Integer getChild1Property()
{
return childProperty_;
}
public void setChild1Property(Integer value)
{
childProperty_ = value;
}
private Integer childProperty_;
}
final class Child1Builder extends ParentBuilder<Child1Builder>
{
public Child1Builder()
{
pending_ = new ConcreteChild1();
}
public Child1Builder withChild1Property(Integer value)
{
pending_.setChild1Property(value);
return this;
}
@Override
protected ParentBase getParent()
{
return pending_;
}
@Override
protected Child1Builder getThis()
{
return this;
}
private final ConcreteChild1 pending_;
}
Como puede ver, el tipo de ParentBuilder
espera cooperar con un tipo derivado para permitirle devolver una instancia correctamente escrita. Su propia referencia no será ParentBuilder
, porque el tipo de this
en ParentBuilder
es, por supuesto, ParentBuilder
, y no, digamos, Child1Builder
como se pretende para mantener el encadenamiento de llamadas "fluido".
Debo el "truco getThis()
" a la entrada del tutorial de Angelika Langer .
No creo que sean necesarios getParent()
y getThis()
, si está dispuesto a aceptar la restricción de que los métodos withXXXProperty()
se llamen de "más jóvenes" a "más antiguos":
class Parent
{
private final long parentProperty;
public long getParentProperty()
{
return parentProperty;
}
public static abstract class Builder<T extends Parent>
{
private long parentProperty;
public Builder<T> withParentProperty( long parentProperty )
{
this.parentProperty = parentProperty;
return this;
}
public abstract T build();
}
public static Builder<?> builder()
{
return new Builder<Parent>()
{
@Override
public Parent build()
{
return new Parent(this);
}
};
}
protected Parent( Builder<?> builder )
{
this.parentProperty = builder.parentProperty;
}
}
class Child1 extends Parent
{
private final int child1Property;
public int getChild1Property()
{
return child1Property;
}
public static abstract class Builder<T extends Child1> extends Parent.Builder<T>
{
private int child1Property;
public Builder<T> withChild1Property( int child1Property )
{
this.child1Property = child1Property;
return this;
}
public abstract T build();
}
public static Builder<?> builder()
{
return new Builder<Child1>()
{
@Override
public Child1 build()
{
return new Child1(this);
}
};
}
protected Child1( Builder<?> builder )
{
super(builder);
this.child1Property = builder.child1Property;
}
}
class Child2 extends Parent
{
private final String child2PropertyA;
private final Object child2PropertyB;
public String getChild2PropertyA()
{
return child2PropertyA;
}
public Object getChild2PropertyB()
{
return child2PropertyB;
}
public static abstract class Builder<T extends Child2> extends Parent.Builder<T>
{
private String child2PropertyA;
private Object child2PropertyB;
public Builder<T> withChild2PropertyA( String child2PropertyA )
{
this.child2PropertyA = child2PropertyA;
return this;
}
public Builder<T> withChild2PropertyB( Object child2PropertyB )
{
this.child2PropertyB = child2PropertyB;
return this;
}
}
public static Builder<?> builder()
{
return new Builder<Child2>()
{
@Override
public Child2 build()
{
return new Child2(this);
}
};
}
protected Child2( Builder<?> builder )
{
super(builder);
this.child2PropertyA = builder.child2PropertyA;
this.child2PropertyB = builder.child2PropertyB;
}
}
class BuilderTest
{
public static void main( String[] args )
{
Child1 child1 = Child1.builder()
.withChild1Property(-3)
.withParentProperty(5L)
.build();
Child2 grandchild = Child2.builder()
.withChild2PropertyA("hello")
.withChild2PropertyB(new Object())
.withParentProperty(10L)
.build();
}
}
Todavía hay algunos ejemplos: el Builder
concreto anónimo en cada método del builder()
y la llamada super()
en cada constructor. (Nota: se asume que cada nivel está diseñado para una mayor capacidad de herencia. Si en algún punto tiene un descendiente final
, puede hacer que la clase constructora sea concreta y el constructor privado).
Pero creo que esta versión es más fácil de seguir, para el siguiente programador que viene y tiene que mantener su código (no genéricos autorreferenciales, para empezar; un Builder<X>
construye Xs
). Y el IMHO que requiere que las propiedades secundarias se establezcan en el constructor antes que las propiedades primarias es una ventaja, tanto en términos de consistencia, como una desventaja en términos de flexibilidad.
Tal vez así sin constructores ?:
interface P {
public Long getParentProperty();
}
interface C1 extends P {
public Integer getChild1Property();
}
interface C2 extends P {
public String getChild2PropertyA();
public Object getChild2PropertyB();
}
abstract class PABC implements P {
@Override public final Long getParentProperty() {
return parentLong;
}
protected Long parentLong;
protected PABC setParentProperty(Long value) {
parentLong = value;
return this;
}
}
final class C1Impl extends PABC implements C1 {
protected C1Impl setParentProperty(Long value) {
super.setParentProperty(value);
return this;
}
@Override public Integer getChild1Property() {
return n;
}
public C1Impl setChild1Property(Integer value) {
n = value;
return this;
}
private Integer n;
}
final class C2Impl extends PABC implements C2 {
private String string;
private Object object;
protected C2Impl setParentProperty(Long value) {
super.setParentProperty(value);
return this;
}
@Override public String getChild2PropertyA() {
return string;
}
@Override public Object getChild2PropertyB() {
return object;
}
C2Impl setChild2PropertyA(String string) {
this.string=string;
return this;
}
C2Impl setChild2PropertyB(Object o) {
this.object=o;
return this;
}
}
public class Myso9138027 {
public static void main(String[] args) {
C1Impl c1 = new C1Impl().setChild1Property(5).setParentProperty(10L);
C2Impl c2 = new C2Impl().setChild2PropertyA("Hello").setParentProperty(10L).setChild2PropertyB(new Object());
}
}
Use genéricos, como sigue:
public interface Parent {
public Long getParentProperty();
}
public interface Child<T> {
public T getChildProperty();
}
Luego, en lugar de Child1, use Child<Integer>
y en lugar de Child2, use Child<String>
.
package so9138027take2;
import java.util.*;
import so9138027take2.C2.Names;
interface P {
public Object getParentProperty(Names name);
enum Names {
i(Integer.class), d(Double.class), s(String.class);
Names(Class<?> clazz) {
this.clazz = clazz;
}
final Class<?> clazz;
}
}
interface C1 extends P {
public Object getChildProperty(Names name);
enum Names {
a(Integer.class), b(Double.class), c(String.class);
Names(Class<?> clazz) {
this.clazz = clazz;
}
final Class<?> clazz;
}
}
interface C2 extends P {
public Object getChildProperty(Names name);
enum Names {
x(Integer.class), y(Double.class), z(String.class);
Names(Class<?> clazz) {
this.clazz = clazz;
}
final Class<?> clazz;
}
}
abstract class PABCImmutable implements P {
public PABCImmutable(PABC parent) {
parentNameToValue = Collections.unmodifiableMap(parent.parentNameToValue);
}
@Override public final Object getParentProperty(Names name) {
return parentNameToValue.get(name);
}
public String toString() {
return parentNameToValue.toString();
}
final Map<Names, Object> parentNameToValue;
}
abstract class PABC implements P {
@Override public final Object getParentProperty(Names name) {
return parentNameToValue.get(name);
}
protected PABC setParentProperty(Names name, Object value) {
if (name.clazz.isInstance(value)) parentNameToValue.put(name, value);
else
throw new RuntimeException("value is not valid for " + name);
return this;
}
public String toString() {
return parentNameToValue.toString();
}
EnumMap<Names, Object> parentNameToValue = new EnumMap<Names, Object>(P.Names.class);
}
final class C1Immutable extends PABCImmutable implements C1 {
public C1Immutable(C1Impl c1) {
super(c1);
nameToValue = Collections.unmodifiableMap(c1.nameToValue);
}
@Override public Object getChildProperty(C1.Names name) {
return nameToValue.get(name);
}
public String toString() {
return super.toString() + nameToValue.toString();
}
final Map<C1.Names, Object> nameToValue;
}
final class C1Impl extends PABC implements C1 {
@Override public Object getChildProperty(C1.Names name) {
return nameToValue.get(name);
}
public Object setChildProperty(C1.Names name, Object value) {
if (name.clazz.isInstance(value)) nameToValue.put(name, value);
else
throw new RuntimeException("value is not valid for " + name);
return this;
}
public String toString() {
return super.toString() + nameToValue.toString();
}
EnumMap<C1.Names, Object> nameToValue = new EnumMap<C1.Names, Object>(C1.Names.class);
}
final class C2Immutable extends PABCImmutable implements C2 {
public C2Immutable(C2Impl c2) {
super(c2);
this.nameToValue = Collections.unmodifiableMap(c2.nameToValue);
}
@Override public Object getChildProperty(C2.Names name) {
return nameToValue.get(name);
}
public String toString() {
return super.toString() + nameToValue.toString();
}
final Map<C2.Names, Object> nameToValue;
}
final class C2Impl extends PABC implements C2 {
@Override public Object getChildProperty(C2.Names name) {
return nameToValue.get(name);
}
public Object setChildProperty(C2.Names name, Object value) {
if (name.clazz.isInstance(value)) {
nameToValue.put(name, value);
} else {
System.out.println("name=" + name + ", value=" + value);
throw new RuntimeException("value is not valid for " + name);
}
return this;
}
public String toString() {
return super.toString() + nameToValue.toString();
}
EnumMap<C2.Names, Object> nameToValue = new EnumMap<C2.Names, Object>(C2.Names.class);
}
public class So9138027take2 {
public static void main(String[] args) {
Object[] parentValues = new Object[] { 1, 2., "foo" };
C1Impl c1 = new C1Impl();
Object[] c1Values = new Object[] { 3, 4., "bar" };
for (P.Names name : P.Names.values())
c1.setParentProperty(name, parentValues[name.ordinal()]);
for (C1.Names name : C1.Names.values())
c1.setChildProperty(name, c1Values[name.ordinal()]);
C2Impl c2 = new C2Impl();
Object[] c2Values = new Object[] { 5, 6., "baz" };
for (P.Names name : P.Names.values())
c2.setParentProperty(name, parentValues[name.ordinal()]);
for (C2.Names name : C2.Names.values())
c2.setChildProperty(name, c2Values[name.ordinal()]);
C1 immutableC1 = new C1Immutable(c1);
System.out.println("child 1: "+immutableC1);
C2 immutableC2 = new C2Immutable(c2);
System.out.println("child 2: "+immutableC2);
}
}