Editar: Lo siento; Deseoso de darle un ejemplo mínimo, no proporcioné suficiente información sobre los requisitos en mi primera versión de esta pregunta.

Tengo dos clases genéricas abstractas. Cooperan y por lo tanto dependen mutuamente. De vez en cuando uno necesita pasar this al otro. Estoy tratando de encontrar una forma segura de hacer esto.

public abstract class AbstractA<T extends AbstractB<? extends AbstractA<T>>> {

    protected void foo() {
        T aB = createB();
        aB.setA(this);
    }

    /** factory method */
    abstract public T createB();

}

public abstract class AbstractB<T extends AbstractA<? extends AbstractB<T>>> {

    private T theA;

    @SuppressWarnings("unchecked")
    public void setA(AbstractA<? extends AbstractB<?>> theA) { // dreamed of parameter list (T theA)
        // Unchecked cast from AbstractA<capture#1-of ? extends AbstractB<?>> to T
        this.theA = (T) theA;
    }

    protected T getA() {
        return theA;
    }

}

Mi pregunta es si puedo encontrar una forma más limpia para evitar el lanzamiento sin marcar en AbstractB.setA(). Tenía la esperanza de declararlo setA(T theA), pero luego la llamada a él no se compilará: el método setA (capture # 1-of? Extiende AbstractA & lt; T & gt;) en el tipo AbstractB & lt; capture # 1-of? extiende AbstractA & lt; T & gt; & gt; no es aplicable para los argumentos (AbstractA & lt; T & gt;). Todavía estoy luchando por entender si el compilador debería saber lo suficiente para permitirlo o no.

Estaba pensando que mi problema puede estar relacionado con el que se discutió en Java error de compilación de genéricos: el método método (clase & lt; captura n. ° 1 de? amplía la interfaz & gt;) en el tipo & lt; type & gt; no es aplicable a los argumentos. Mi elenco sin control se inspiró a partir de ahí. Me gustó la respuesta de Tom Hawtin - abordar, pero no he encontrado la forma de aplicarla a mi situación.

Mi usuario declarará subclases concretas e instanciará una ConcreteA y cualquier número de ConcreteB s:

public class ConcreteA extends AbstractA<ConcreteB> {

    @Override
    public ConcreteB createB() {
        return new ConcreteB();
    }

    public void concreteAMethod() {
        // ...
    }

}

public class ConcreteB extends AbstractB<ConcreteA> {

    public void bar() {
        ConcreteA a = getA();
        a.concreteAMethod();
    }

}

(class AbstractA<T extends AbstractB<? extends AbstractA<T>>> parece un poco complicado; pensé que lo necesitaba para que las subclases concretas conocieran los tipos exactos de las demás, pero aparentemente no me da eso).

-4
Ole V.V. 26 ene. 2016 a las 14:48

5 respuestas

La mejor respuesta

Me he dado cuenta de que mi problema realmente surgió de meter dos conceptos en la jerarquía AbstractA / ConcreteA que no encajaban juntos. Aunque tal vez no sea interesante para muchos, estoy publicando esta información por dos razones: (1) siento que le debo a Chris Wohlert la respuesta que me he encontrado (2) lo que es más importante, me encantaría inspirar a cualquier otra persona que se enfrente a un problema similar. problema de genéricos para revisar su diseño desde un nivel más alto que simplemente resolver el problema de los genéricos y / o el reparto de clases. Ciertamente me ayudó. El problema del elenco / genéricos era una señal de que algo más fundamental no estaba del todo bien.

public abstract class AbstractA {

    public void foo() {
        AbstractB aB = createB();
        aB.setA(this);
    }

    /** factory method */
    abstract public AbstractB createB();

}

public abstract class AbstractB {

    private AbstractA theA;

    public void setA(AbstractA theA) {
        this.theA = theA;
    }

    // methods that use theA

}

Sin genéricos y sin elenco de clases. Sacando las cosas que no pertenecían a la jerarquía de clases A en ConcreteC (sin AbstractC):

public class Client {

    public void putTheActTogether() {
        ConcreteC theC = new ConcreteC();

        // the concrete A
        AbstractA theA = new AbstractA() {
            @Override
            public AbstractB createB() {
                return new ConcreteB(theC);
            }
        };
        // call methods in theA
    }

}

public class ConcreteB extends AbstractB {

    private final ConcreteC c;

    public ConcreteB(ConcreteC c) {
        super();
        this.c = c;
    }

    public void bar() {
        c.concreteCMethod();
    }

}

public class ConcreteC {

    public void concreteCMethod() { // was concreteAMethod(); moved and renamed
        // ...
    }

}

El cliente necesita algunas líneas más que antes. En mi código del mundo real necesitaba duplicar un campo final en AbstractA y ConcreteC, pero tenía sentido hacerlo. Todo en todo lo considero un precio bajo para un diseño que por lo demás es puro y simple.

0
Ole V.V. 23 feb. 2016 a las 10:35

Una fábrica puede solucionarlo:

public abstract class AbstractA {

    public void abstractAMethod() {
        // ...
    }

}

public abstract class AbstractB<A> {

    private A theA;

    public void setA(A theA) {
        this.theA = theA;
    }

    protected A getA() {
        return theA;
    }

}

public abstract class AbstractFactory<A extends AbstractA, B extends AbstractB<A>> {

    private A theA = createA();

    public A getA() {
        return theA ;
    }

    public B getNextB() {
        B newB = createB();
        newB.setA(theA);
        return newB;
    }

    protected abstract A createA();
    protected abstract B createB();
}

Ahora la usuaria puede ir: Ahora el usuario puede ir:

public class ConcreteA extends AbstractA {

    public void concreteAMethod() {
        // ...
    }

}

public class ConcreteB extends AbstractB<ConcreteA> {

    public void bar() {
        ConcreteA a = getA();
        a.abstractAMethod();
        a.concreteAMethod();
    }

}

public class ConcreteFactory extends AbstractFactory<ConcreteA, ConcreteB> {

    @Override
    protected ConcreteA createA() {
        return new ConcreteA();
    }

    @Override
    protected ConcreteB createB() {
        return new ConcreteB();
    }

}

Sin embargo, no creo que sea una aplicación típica del patrón de fábrica abstracto ...

@Chris Wohlert, me di por vencido en mi código de producción ya que consideré que la fábrica era excesiva, pero no podía dejar de lado la pregunta teórica.

0
Ole V.V. 4 feb. 2016 a las 11:47

Cada clase abstracta se parametrizaría con dos parámetros de tipo, uno para la clase concreta real de A y otro para la clase concreta real de B:

public abstract class AbstractA<A extends AbstractA<A,B>, B extends AbstractB<A,B>> {

    protected void foo() {
        B aB = createB();
        aB.setA(getThis());
    }

    abstract public A getThis();
    abstract public B createB();

}

public abstract class AbstractB<A extends AbstractA<A,B>, B extends AbstractB<A,B>> {

    private A theA;

    public void setA(A theA) {
        this.theA = theA;
    }

    protected A getA() {
        return theA;
    }

}

public class ConcreteA extends AbstractA<ConcreteA, ConcreteB> {

    @Override
    public ConcreteA getThis() {
        return this;
    }

    @Override
    public ConcreteB createB() {
        return new ConcreteB();
    }

    public void concreteAMethod() {
        // ...
    }

}

public class ConcreteB extends AbstractB<ConcreteA, ConcreteB> {

    public void bar() {
        ConcreteA a = getA();
        a.concreteAMethod();
    }

}
0
newacct 29 ene. 2016 a las 09:47

Creo que ahora entendí por qué no puedo declarar public void setA(T theA) en AbstractB y luego llamarlo como aB.setA(this) en foo(). Supongamos que tuviéramos:

class IntermediateConcreteA extends AbstractA<ConcreteB> {

    @Override
    public ConcreteB createB() {
        return new ConcreteB();
    }

}

class SubConcreteA1 extends IntermediateConcreteA {}

class SubConcreteA2 extends IntermediateConcreteA {}

class ConcreteB extends AbstractB<SubConcreteA2> {}

Ahora, si tengo un SubConcreteA1 y lo llamo foo(), entonces createB() devolverá un objeto que puede pasar como AbstractB<SubConcreteA2> pero no puede pasar como AbstractB<SubConcreteA1>. Por lo tanto, su setA() no debería aceptar this como argumento. El mensaje de error del compilador es lógico después de todo.

0
Ole V.V. 27 ene. 2016 a las 20:55

Si lo he entendido correctamente, esto debería crear el enlace que desea.

class Demo {

    public static void main(String[] args) {
        ConcreteA a = new ConcreteA();
        ConcreteB b = new ConcreteB();
        a.foo(b);
        b = (ConcreteB) a.getB();
    }
}

abstract class AbstractA<T extends AbstractB<?>>{

    private AbstractB<?> b;

    public AbstractB<?> getB(){
        return b;
    }

    void foo(AbstractB<?> aB) {
        b = aB;
        aB.bar(this);
    }
}

abstract class AbstractB<T extends AbstractA<?>> {

    private AbstractA<?> a;

    public AbstractA<?> getA(){
        return a;
    }

    public void bar(AbstractA<?> theA) {
        a = theA;
        theA.foo(this);
    }
}

class ConcreteA extends AbstractA<ConcreteB>{

}

class ConcreteB extends AbstractB<ConcreteA>{

}

Creo que esto es lo que terminaste en ti mismo. No puedo quitar el molde a ConcreteB, getB () simplemente no puede estar seguro del tipo que tiene. Ahora veo por qué tenía varias declaraciones genéricas en su declaración. :)

Si está dispuesto a hacerlo, continúe buscando y publique su propia respuesta si encuentra una, me encantaría verla.

Espero que resolver la mitad de tu problema sirva para algo. ;)

0
Chris Wohlert 26 ene. 2016 a las 15:07